fix enroll and reenroll relationship
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
|
from abc import ABC
|
||||||
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from enum import Enum
|
||||||
from typing import NotRequired, Self, TypedDict
|
from typing import NotRequired, Self, TypedDict
|
||||||
|
|
||||||
from layercake.dateutils import now, ttl
|
from layercake.dateutils import now, ttl
|
||||||
@@ -32,15 +35,16 @@ Subscription = TypedDict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LinkedEntity(str):
|
class Kind(str, Enum):
|
||||||
def __new__(cls, id: str, type: str) -> Self:
|
ORDER = 'ORDER'
|
||||||
return super().__new__(cls, '#'.join([type.lower(), id]))
|
ENROLLMENT = 'ENROLLMENT'
|
||||||
|
|
||||||
def __init__(self, id: str, type: str) -> None:
|
|
||||||
# __init__ is used to store the parameters for later reference.
|
@dataclass(frozen=True)
|
||||||
# For immutable types like str, __init__ cannot change the instance's value.
|
class LinkedEntity(ABC):
|
||||||
self.id = id
|
id: str
|
||||||
self.type = type
|
kind: Kind
|
||||||
|
table_name: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class DeduplicationConflictError(Exception):
|
class DeduplicationConflictError(Exception):
|
||||||
@@ -76,29 +80,27 @@ def enroll(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Relationships between this enrollment and its related entities
|
# Relationships between this enrollment and its related entities
|
||||||
for parent_entity in linked_entities:
|
for entity in linked_entities:
|
||||||
perent_id = parent_entity.id
|
|
||||||
entity_sk = f'LINKED_ENTITIES#{parent_entity.type}'
|
|
||||||
keyprefix = parent_entity.type.lower()
|
|
||||||
|
|
||||||
# Parent knows the child
|
# Parent knows the child
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': perent_id,
|
'id': entity.id,
|
||||||
'sk': f'{entity_sk}#CHILD',
|
'sk': f'LINKED_ENTITIES#{entity.kind.value}#CHILD',
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
f'{keyprefix}_id': enrollment.id,
|
'enrollment_id': enrollment.id,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
|
table_name=entity.table_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
keyprefix = entity.kind.value.lower()
|
||||||
# Child knows the parent
|
# Child knows the parent
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': enrollment.id,
|
'id': enrollment.id,
|
||||||
'sk': f'{entity_sk}#PARENT',
|
'sk': f'LINKED_ENTITIES#{entity.kind.value}#PARENT',
|
||||||
'created_at': now_,
|
'created_at': now_,
|
||||||
f'{keyprefix}_id': perent_id,
|
f'{keyprefix}_id': entity.id,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
class Course:
|
class Course:
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
access_period: int
|
||||||
|
|
||||||
|
|
||||||
def _get_courses(ids: set) -> tuple[Course, ...]:
|
def _get_courses(ids: set) -> tuple[Course, ...]:
|
||||||
@@ -93,6 +94,7 @@ def _get_courses(ids: set) -> tuple[Course, ...]:
|
|||||||
Course(
|
Course(
|
||||||
id=idx,
|
id=idx,
|
||||||
name=obj['name'],
|
name=obj['name'],
|
||||||
|
access_period=obj['access_period'],
|
||||||
)
|
)
|
||||||
for idx, obj in result.items()
|
for idx, obj in result.items()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ from layercake.dynamodb import (
|
|||||||
|
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE
|
from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE
|
||||||
from enrollment import LinkedEntity, enroll
|
from enrollment import (
|
||||||
|
Kind,
|
||||||
|
LinkedEntity,
|
||||||
|
enroll,
|
||||||
|
)
|
||||||
from schemas import Course, Enrollment, User
|
from schemas import Course, Enrollment, User
|
||||||
|
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
@@ -89,7 +93,15 @@ def _handler(record: Course, context: dict) -> Enrollment:
|
|||||||
enrollment,
|
enrollment,
|
||||||
persistence_layer=enrollment_layer,
|
persistence_layer=enrollment_layer,
|
||||||
deduplication_window={'offset_days': 90},
|
deduplication_window={'offset_days': 90},
|
||||||
linked_entities=frozenset({LinkedEntity(context['order_id'], 'ORDER')}),
|
linked_entities=frozenset(
|
||||||
|
{
|
||||||
|
LinkedEntity(
|
||||||
|
id=context['order_id'],
|
||||||
|
kind=Kind.ORDER,
|
||||||
|
table_name=ORDER_TABLE,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return enrollment
|
return enrollment
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if 's3_uri' not in cert:
|
||||||
|
raise ValueError('Template URI is missing')
|
||||||
|
|
||||||
# Send template URI and data to Paperforge API to generate a PDF
|
# Send template URI and data to Paperforge API to generate a PDF
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
PAPERFORGE_API,
|
PAPERFORGE_API,
|
||||||
@@ -79,10 +82,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
timeout=5,
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
object_key = f'issuedcerts/{enrollment_id}.pdf'
|
object_key = f'certs/{enrollment_id}.pdf'
|
||||||
s3_uri = f's3://{BUCKET_NAME}/{object_key}'
|
s3_uri = f's3://{BUCKET_NAME}/{object_key}'
|
||||||
|
|
||||||
s3_client.put_object(
|
s3_client.put_object(
|
||||||
@@ -93,10 +97,10 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f'PDF uploaded successfully to {s3_uri}')
|
logger.debug(f'PDF uploaded successfully to {s3_uri}')
|
||||||
except KeyError:
|
except ValueError as exc:
|
||||||
# PDF generation fails if template URI is missing
|
# PDF generation fails if template URI is missing
|
||||||
s3_uri = None
|
s3_uri = None
|
||||||
logger.debug('Template URI is missing')
|
logger.exception(exc)
|
||||||
except requests.exceptions.RequestException as exc:
|
except requests.exceptions.RequestException as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, SortKey, TransactKey
|
|||||||
|
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import ENROLLMENT_TABLE
|
from config import ENROLLMENT_TABLE
|
||||||
from enrollment import LinkedEntity, enroll
|
from enrollment import Kind, LinkedEntity, enroll
|
||||||
from schemas import Course, Enrollment, User
|
from schemas import Course, Enrollment, User
|
||||||
|
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
@@ -52,7 +52,12 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
'offset_days': metadata['dedup_window_offset_days'],
|
'offset_days': metadata['dedup_window_offset_days'],
|
||||||
},
|
},
|
||||||
linked_entities=frozenset(
|
linked_entities=frozenset(
|
||||||
{LinkedEntity(new_image['id'], 'ENROLLMENT')},
|
{
|
||||||
|
LinkedEntity(
|
||||||
|
id=new_image['id'],
|
||||||
|
kind=Kind.ENROLLMENT,
|
||||||
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
persistence_layer=dyn,
|
persistence_layer=dyn,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
pk=old_image['id'],
|
pk=old_image['id'],
|
||||||
sk='0',
|
sk='0',
|
||||||
),
|
),
|
||||||
update_expr='SET access_expired = :true, \
|
update_expr='SET access_expired = :true, updated_at = :now',
|
||||||
updated_at = :now',
|
|
||||||
expr_attr_values={
|
expr_attr_values={
|
||||||
':true': True,
|
':true': True,
|
||||||
':now': now_,
|
':now': now_,
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ Resources:
|
|||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.issue_cert.lambda_handler
|
Handler: events.issue_cert.lambda_handler
|
||||||
|
# Timeout: 30
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
Policies:
|
Policies:
|
||||||
@@ -331,10 +332,10 @@ Resources:
|
|||||||
old_image:
|
old_image:
|
||||||
status: [IN_PROGRESS]
|
status: [IN_PROGRESS]
|
||||||
|
|
||||||
EventCertReportingAppendCertFunction:
|
EventReportingAppendCertFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.cert_reporting.append_cert.lambda_handler
|
Handler: events.reporting.append_cert.lambda_handler
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
Policies:
|
Policies:
|
||||||
@@ -346,23 +347,19 @@ Resources:
|
|||||||
Properties:
|
Properties:
|
||||||
Pattern:
|
Pattern:
|
||||||
resources: [!Ref EnrollmentTable]
|
resources: [!Ref EnrollmentTable]
|
||||||
|
detail-type: [MODIFY]
|
||||||
detail:
|
detail:
|
||||||
keys:
|
keys:
|
||||||
sk: ["0"]
|
sk: ["0"]
|
||||||
new_image:
|
new_image:
|
||||||
status: [COMPLETED]
|
status: [COMPLETED]
|
||||||
cert:
|
|
||||||
exists: true
|
|
||||||
org_id:
|
org_id:
|
||||||
exists: true
|
- exists: true
|
||||||
old_image:
|
|
||||||
cert:
|
|
||||||
exists: false
|
|
||||||
|
|
||||||
EventCertReportingSendReportEmailFunction:
|
EventReportingSendReportEmailFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.cert_reporting.send_report_email.lambda_handler
|
Handler: events.reporting.send_report_email.lambda_handler
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
Policies:
|
Policies:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ def pytest_configure():
|
|||||||
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
|
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
|
||||||
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
|
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
|
||||||
os.environ['BUCKET_NAME'] = 'saladeaula.digital'
|
os.environ['BUCKET_NAME'] = 'saladeaula.digital'
|
||||||
|
os.environ['LOG_LEVEL'] = 'DEBUG'
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import app.events.cert_reporting.append_cert as app
|
import app.events.reporting.append_cert as app
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
from layercake.dateutils import now
|
from layercake.dateutils import now
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import app.events.cert_reporting.send_report_email as app
|
import app.events.reporting.send_report_email as app
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
DynamoDBPersistenceLayer,
|
DynamoDBPersistenceLayer,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import app.events.enroll as app
|
import app.events.enroll as app
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
|
|
||||||
|
|
||||||
def test_enroll(
|
def test_enroll(
|
||||||
@@ -8,12 +8,26 @@ def test_enroll(
|
|||||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
lambda_context: LambdaContext,
|
lambda_context: LambdaContext,
|
||||||
):
|
):
|
||||||
|
order_id = 'cpYSbBcie2NDbZhDKCxCih'
|
||||||
event = {
|
event = {
|
||||||
'detail': {
|
'detail': {
|
||||||
'new_image': {
|
'new_image': {
|
||||||
'id': 'cpYSbBcie2NDbZhDKCxCih',
|
'id': order_id,
|
||||||
'sk': 'generated_items',
|
'sk': 'generated_items',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert app.lambda_handler(event, lambda_context) # type: ignore
|
assert app.lambda_handler(event, lambda_context) # type: ignore
|
||||||
|
|
||||||
|
# Parent knows the child
|
||||||
|
order_entity = dynamodb_persistence_layer.collection.get_item(
|
||||||
|
KeyPair(order_id, 'LINKED_ENTITIES#ORDER#CHILD')
|
||||||
|
)
|
||||||
|
assert order_entity
|
||||||
|
|
||||||
|
# Child knows the parent
|
||||||
|
enrollment_entity = dynamodb_persistence_layer.collection.get_item(
|
||||||
|
KeyPair(order_entity['enrollment_id'], 'LINKED_ENTITIES#ORDER#PARENT'),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert enrollment_entity['order_id'] == order_id
|
||||||
|
|||||||
@@ -30,19 +30,19 @@ def test_reenroll(
|
|||||||
assert app.lambda_handler(event, lambda_context) # type: ignore
|
assert app.lambda_handler(event, lambda_context) # type: ignore
|
||||||
|
|
||||||
# Parent knows the child
|
# Parent knows the child
|
||||||
child_entity = dynamodb_persistence_layer.collection.get_item(
|
current_entity = dynamodb_persistence_layer.collection.get_item(
|
||||||
KeyPair(
|
KeyPair(
|
||||||
pk=parent_id,
|
pk=parent_id,
|
||||||
sk='LINKED_ENTITIES#ENROLLMENT#CHILD',
|
sk='LINKED_ENTITIES#ENROLLMENT#CHILD',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert child_entity
|
assert current_entity
|
||||||
|
|
||||||
# Child knows the parent
|
# Child knows the parent
|
||||||
parent_entity = dynamodb_persistence_layer.collection.get_item(
|
new_entity = dynamodb_persistence_layer.collection.get_item(
|
||||||
KeyPair(
|
KeyPair(
|
||||||
pk=child_entity['enrollment_id'],
|
pk=current_entity['enrollment_id'],
|
||||||
sk='LINKED_ENTITIES#ENROLLMENT#PARENT',
|
sk='LINKED_ENTITIES#ENROLLMENT#PARENT',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert parent_entity['enrollment_id'] == parent_id
|
assert new_entity['enrollment_id'] == parent_id
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import urllib.parse as parse
|
import urllib.parse as parse
|
||||||
|
from os import rename
|
||||||
|
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
@@ -73,16 +74,16 @@ def get_enrollment(id: str):
|
|||||||
record = enrollment_layer.collection.get_items(
|
record = enrollment_layer.collection.get_items(
|
||||||
TransactKey(id)
|
TransactKey(id)
|
||||||
+ SortKey('0')
|
+ SortKey('0')
|
||||||
|
+ SortKey('ORG', rename_key='org')
|
||||||
# + SortKey('STARTED', rename_key='started_at', path_spec='started_at')
|
# + SortKey('STARTED', rename_key='started_at', path_spec='started_at')
|
||||||
# + SortKey('COMPLETED', rename_key='completed_at', path_spec='completed_at')
|
# + SortKey('COMPLETED', rename_key='completed_at', path_spec='completed_at')
|
||||||
# + SortKey('FAILED', rename_key='failed_at', path_spec='failed_at')
|
# + SortKey('FAILED', rename_key='failed_at', path_spec='failed_at')
|
||||||
+ SortKey('CANCELED', rename_key='canceled')
|
+ SortKey('CANCELED', rename_key='canceled')
|
||||||
+ SortKey('ARCHIVED', rename_key='archived_at', path_spec='archived_at')
|
# + SortKey('ARCHIVED', rename_key='archived_at', path_spec='archived_at')
|
||||||
+ SortKey('CANCEL_POLICY', rename_key='cancel_policy')
|
+ SortKey('CANCEL_POLICY', rename_key='cancel_policy')
|
||||||
+ SortKey('LOCK', rename_key='lock')
|
+ SortKey('LOCK', rename_key='lock')
|
||||||
+ SortKey('parent_vacancy', path_spec='vacancy')
|
+ SortKey('parent_vacancy', path_spec='vacancy')
|
||||||
+ SortKey('author')
|
+ SortKey('author')
|
||||||
+ SortKey('tenant')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
events = enrollment_layer.collection.query(KeyPair(id, 'SCHEDULE#'))
|
events = enrollment_layer.collection.query(KeyPair(id, 'SCHEDULE#'))
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
|||||||
def download(id: str):
|
def download(id: str):
|
||||||
params = {
|
params = {
|
||||||
'Bucket': BUCKET_NAME,
|
'Bucket': BUCKET_NAME,
|
||||||
'Key': f'issuedcerts/{id}.pdf',
|
'Key': f'certs/{id}.pdf',
|
||||||
'ResponseContentDisposition': f'attachment; filename="{id}.pdf"',
|
'ResponseContentDisposition': f'attachment; filename="{id}.pdf"',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ def update_progress(
|
|||||||
key=KeyPair(id, '0'),
|
key=KeyPair(id, '0'),
|
||||||
update_expr='SET progress = :progress, \
|
update_expr='SET progress = :progress, \
|
||||||
#status = :in_progress, \
|
#status = :in_progress, \
|
||||||
started_at = :now, \
|
started_at = if_not_exists(started_at, :now), \
|
||||||
updated_at = :now',
|
updated_at = :now',
|
||||||
cond_expr='#status = :pending',
|
cond_expr='#status = :pending',
|
||||||
expr_attr_names={
|
expr_attr_names={
|
||||||
@@ -69,15 +69,6 @@ def update_progress(
|
|||||||
},
|
},
|
||||||
exc_cls=EnrollmentConflictError,
|
exc_cls=EnrollmentConflictError,
|
||||||
)
|
)
|
||||||
# Record the start date if it does not already exist
|
|
||||||
transact.put(
|
|
||||||
item={
|
|
||||||
'id': id,
|
|
||||||
'sk': 'STARTED',
|
|
||||||
'created_at': now_,
|
|
||||||
},
|
|
||||||
cond_expr='attribute_not_exists(sk)',
|
|
||||||
)
|
|
||||||
# Schedule a reminder for inactivity
|
# Schedule a reminder for inactivity
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
@@ -192,7 +183,7 @@ def _set_status_as_completed(
|
|||||||
update_expr='SET #status = :completed, \
|
update_expr='SET #status = :completed, \
|
||||||
progress = :progress, \
|
progress = :progress, \
|
||||||
score = :score, \
|
score = :score, \
|
||||||
completed_at = :now, \
|
completed_at = if_not_exists(completed_at, :now), \
|
||||||
updated_at = :now',
|
updated_at = :now',
|
||||||
cond_expr='#status = :in_progress',
|
cond_expr='#status = :in_progress',
|
||||||
expr_attr_names={'#status': 'status'},
|
expr_attr_names={'#status': 'status'},
|
||||||
@@ -205,14 +196,6 @@ def _set_status_as_completed(
|
|||||||
},
|
},
|
||||||
exc_cls=EnrollmentConflictError,
|
exc_cls=EnrollmentConflictError,
|
||||||
)
|
)
|
||||||
transact.put(
|
|
||||||
item={
|
|
||||||
'id': id,
|
|
||||||
'sk': 'COMPLETED',
|
|
||||||
'created_at': now_,
|
|
||||||
},
|
|
||||||
cond_expr='attribute_not_exists(sk)',
|
|
||||||
)
|
|
||||||
|
|
||||||
if cert_exp_interval:
|
if cert_exp_interval:
|
||||||
transact.put(
|
transact.put(
|
||||||
@@ -293,7 +276,7 @@ def _set_status_as_failed(
|
|||||||
progress = :progress, \
|
progress = :progress, \
|
||||||
score = :score, \
|
score = :score, \
|
||||||
access_expired = :true, \
|
access_expired = :true, \
|
||||||
failed_at = :now, \
|
failed_at = if_not_exists(failed_at, :now), \
|
||||||
updated_at = :now',
|
updated_at = :now',
|
||||||
cond_expr='#status = :in_progress',
|
cond_expr='#status = :in_progress',
|
||||||
expr_attr_names={'#status': 'status'},
|
expr_attr_names={'#status': 'status'},
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ def test_start_progress(
|
|||||||
r = dynamodb_persistence_layer.collection.query(
|
r = dynamodb_persistence_layer.collection.query(
|
||||||
PartitionKey('d9da85f2-e09f-472d-9515-3d91d70f1e8a')
|
PartitionKey('d9da85f2-e09f-472d-9515-3d91d70f1e8a')
|
||||||
)
|
)
|
||||||
assert any(item.get('sk') == 'STARTED' for item in r['items'])
|
docx = next((x for x in r['items'] if x['sk'] == '0'), {})
|
||||||
|
assert 'started_at' in docx
|
||||||
assert any(
|
assert any(
|
||||||
item.get('sk') == 'SCHEDULE#REMINDER_NO_ACTIVITY_AFTER_7_DAYS'
|
item.get('sk') == 'SCHEDULE#REMINDER_NO_ACTIVITY_AFTER_7_DAYS'
|
||||||
for item in r['items']
|
for item in r['items']
|
||||||
)
|
)
|
||||||
assert len(r['items']) == 3
|
assert len(r['items']) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_update_progress(
|
def test_update_progress(
|
||||||
@@ -110,8 +111,9 @@ def test_set_as_completed(
|
|||||||
PartitionKey('6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8')
|
PartitionKey('6c7e3d9b-f5d1-4da4-9e55-0825bb6ff2b8')
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(r['items']) == 8
|
docx = next((x for x in r['items'] if x['sk'] == '0'), {})
|
||||||
assert any(item.get('sk') == 'COMPLETED' for item in r['items'])
|
assert 'completed_at' in docx
|
||||||
|
assert len(r['items']) == 7
|
||||||
assert any(item.get('sk') == 'LOCK' for item in r['items'])
|
assert any(item.get('sk') == 'LOCK' for item in r['items'])
|
||||||
assert any(
|
assert any(
|
||||||
item.get('sk') == 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS'
|
item.get('sk') == 'SCHEDULE#REMINDER_CERT_EXPIRATION_BEFORE_30_DAYS'
|
||||||
|
|||||||
46
konviva-events/uv.lock
generated
46
konviva-events/uv.lock
generated
@@ -31,14 +31,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "authlib"
|
name = "authlib"
|
||||||
version = "1.6.1"
|
version = "1.6.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cryptography" },
|
{ name = "cryptography" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" },
|
{ url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -430,6 +430,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
|
{ url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "joserfc"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/26/a0/4b8dfecc8ec3c15aa1f2ff7d5b947344378b5b595ce37c8a8fe6e25c1400/joserfc-1.4.0.tar.gz", hash = "sha256:e8c2f327bf10a937d284d57e9f8aec385381e5e5850469b50a7dade1aba59759", size = 196339, upload-time = "2025-10-09T07:47:00.835Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/05/342459b7629c6fcb5f99a646886ee2904491955b8cce6b26b0b9a498f67c/joserfc-1.4.0-py3-none-any.whl", hash = "sha256:46917e6b53f1ec0c7e20d34d6f3e6c27da0fa43d0d4ebfb89aada7c86582933a", size = 66390, upload-time = "2025-10-09T07:46:59.591Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonlines"
|
name = "jsonlines"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
@@ -485,7 +497,7 @@ dev = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.9.14"
|
version = "0.10.1"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
@@ -494,6 +506,7 @@ dependencies = [
|
|||||||
{ name = "dictdiffer" },
|
{ name = "dictdiffer" },
|
||||||
{ name = "ftfy" },
|
{ name = "ftfy" },
|
||||||
{ name = "glom" },
|
{ name = "glom" },
|
||||||
|
{ name = "joserfc" },
|
||||||
{ name = "meilisearch" },
|
{ name = "meilisearch" },
|
||||||
{ name = "orjson" },
|
{ name = "orjson" },
|
||||||
{ name = "passlib" },
|
{ name = "passlib" },
|
||||||
@@ -501,7 +514,7 @@ dependencies = [
|
|||||||
{ name = "pycpfcnpj" },
|
{ name = "pycpfcnpj" },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
{ name = "pydantic-extra-types" },
|
{ name = "pydantic-extra-types" },
|
||||||
{ name = "pyjwt" },
|
{ name = "python-multipart" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "smart-open", extra = ["s3"] },
|
{ name = "smart-open", extra = ["s3"] },
|
||||||
@@ -512,11 +525,12 @@ dependencies = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "arnparse", specifier = ">=0.0.2" },
|
{ name = "arnparse", specifier = ">=0.0.2" },
|
||||||
{ name = "authlib", specifier = ">=1.6.1" },
|
{ name = "authlib", specifier = ">=1.6.5" },
|
||||||
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" },
|
{ name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.18.0" },
|
||||||
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
{ name = "dictdiffer", specifier = ">=0.9.0" },
|
||||||
{ name = "ftfy", specifier = ">=6.3.1" },
|
{ name = "ftfy", specifier = ">=6.3.1" },
|
||||||
{ name = "glom", specifier = ">=24.11.0" },
|
{ name = "glom", specifier = ">=24.11.0" },
|
||||||
|
{ name = "joserfc", specifier = ">=1.2.2" },
|
||||||
{ name = "meilisearch", specifier = ">=0.34.0" },
|
{ name = "meilisearch", specifier = ">=0.34.0" },
|
||||||
{ name = "orjson", specifier = ">=3.10.15" },
|
{ name = "orjson", specifier = ">=3.10.15" },
|
||||||
{ name = "passlib", specifier = ">=1.7.4" },
|
{ name = "passlib", specifier = ">=1.7.4" },
|
||||||
@@ -524,7 +538,7 @@ requires-dist = [
|
|||||||
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
{ name = "pycpfcnpj", specifier = ">=1.8" },
|
||||||
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.6" },
|
||||||
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
{ name = "pydantic-extra-types", specifier = ">=2.10.3" },
|
||||||
{ name = "pyjwt", specifier = ">=2.10.1" },
|
{ name = "python-multipart", specifier = ">=0.0.20" },
|
||||||
{ name = "pytz", specifier = ">=2025.1" },
|
{ name = "pytz", specifier = ">=2025.1" },
|
||||||
{ name = "requests", specifier = ">=2.32.3" },
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
|
{ name = "smart-open", extras = ["s3"], specifier = ">=7.1.0" },
|
||||||
@@ -761,15 +775,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyjwt"
|
|
||||||
version = "2.10.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.4.1"
|
version = "8.4.1"
|
||||||
@@ -821,6 +826,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
|
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-multipart"
|
||||||
|
version = "0.0.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2025.2"
|
version = "2025.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user