From 2b86c234cc6ee39118fe6d4bc912259435c5bd5e Mon Sep 17 00:00:00 2001 From: rrimpila Date: Fri, 27 Mar 2026 16:43:29 +0200 Subject: [PATCH 1/4] LABP-277 add serializers for set fields --- .../sisu/enrolment_calculation_config.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/funidata_utils/schemas/sisu/enrolment_calculation_config.py b/funidata_utils/schemas/sisu/enrolment_calculation_config.py index 99d0cf5..a700650 100644 --- a/funidata_utils/schemas/sisu/enrolment_calculation_config.py +++ b/funidata_utils/schemas/sisu/enrolment_calculation_config.py @@ -3,8 +3,9 @@ # ------------------------------------------------------------------------------ from typing import Annotated, Literal, Union -from pydantic import BaseModel, Field, conset +from pydantic import BaseModel, Field, conset, field_serializer +from funidata_utils.schemas.common_serializers import serialize_as_list from funidata_utils.schemas.sisu.base import SisBase from funidata_utils.schemas.sisu.common import ( OTM_ID_REGEX_VALIDATED_STR, LocalizedString, STRIPPED_STR, sis_code_urn_pattern, CreditRange, @@ -19,6 +20,11 @@ class AttainedCreditRangeRule(BaseModel): creditRange: CreditRange | None = None creditOrder: Literal['NONE', 'ASCENDING', 'DESCENDING'] + @field_serializer("educationIds") + def serialize_set_as_list(self, v, _info) -> list[str] | None: + serialized_list = serialize_as_list(v) + return serialized_list + class CompulsoryFormalPrerequisitesRule(BaseModel): type: Literal['CompulsoryFormalPrerequisites'] = 'CompulsoryFormalPrerequisites' @@ -32,16 +38,31 @@ class EducationRule(BaseModel): type: Literal['Education'] = 'Education' educationGroupIds: conset(OTM_ID_REGEX_VALIDATED_STR, min_length=1) # noqa + @field_serializer("educationGroupIds") + def serialize_set_as_list(self, v, _info) -> list[str] | None: + serialized_list = serialize_as_list(v) + return serialized_list + class EducationTypeRule(BaseModel): type: Literal['EducationType'] = 'EducationType' educationTypeUrns: conset(Annotated[STRIPPED_STR, Field(pattern=sis_code_urn_pattern('education-type'))], min_length=1) # noqa + @field_serializer("educationTypeUrns") + def serialize_set_as_list(self, v, _info) -> list[str] | None: + serialized_list = serialize_as_list(v) + return serialized_list + class EnrolmentForCourseUnitRealisationRule(BaseModel): type: Literal['EnrolmentForCourseUnitRealisation'] = 'EnrolmentForCourseUnitRealisation' courseUnitRealisationIds: conset(OTM_ID_REGEX_VALIDATED_STR, min_length=1) # noqa + @field_serializer("courseUnitRealisationIds") + def serialize_set_as_list(self, v, _info) -> list[str] | None: + serialized_list = serialize_as_list(v) + return serialized_list + class NotAlreadyEnrolledToAssessmentItemRule(BaseModel): type: Literal['NotAlreadyEnrolledToAssessmentItemRule'] = 'NotAlreadyEnrolledToAssessmentItemRule' @@ -51,6 +72,11 @@ class PersonGroupMembershipRule(BaseModel): type: Literal['PersonGroupMembership'] = 'PersonGroupMembership' personGroupIds: conset(OTM_ID_REGEX_VALIDATED_STR, min_length=1) # noqa + @field_serializer("personGroupIds") + def serialize_set_as_list(self, v, _info) -> list[str] | None: + serialized_list = serialize_as_list(v) + return serialized_list + class RecommendedFormalPrerequisitesRule(BaseModel): type: Literal['RecommendedFormalPrerequisites'] = 'RecommendedFormalPrerequisites' From de81394162c9095689327252e9e246c2ab79ed2e Mon Sep 17 00:00:00 2001 From: rrimpila Date: Mon, 30 Mar 2026 13:09:26 +0300 Subject: [PATCH 2/4] LABP-277 Add tweet text max length validation --- .../schemas/sisu/course_unit_realisation.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/funidata_utils/schemas/sisu/course_unit_realisation.py b/funidata_utils/schemas/sisu/course_unit_realisation.py index 90e5985..c3e81e2 100644 --- a/funidata_utils/schemas/sisu/course_unit_realisation.py +++ b/funidata_utils/schemas/sisu/course_unit_realisation.py @@ -3,7 +3,7 @@ from datetime import date, datetime from typing import Annotated, Literal, Union -from pydantic import BaseModel, Field, conset, conlist, field_serializer +from pydantic import BaseModel, Field, conset, conlist, field_serializer, field_validator from funidata_utils.schemas.common_serializers import serialize_as_list from funidata_utils.schemas.sisu.base import SisBase, HashableBaseModel @@ -15,6 +15,8 @@ from funidata_utils.schemas.sisu.course_unit import CooperationNetworkDetails +TWEET_TEXT_MAX_LENGTH = 160 + class LiteratureName(HashableBaseModel): localId: str type: Literal["LiteratureName"] @@ -124,6 +126,14 @@ class CourseUnitRealisation(SisBase): cooperationNetworkDetails: CooperationNetworkDetails | None = None copyDetails: CopyDetails | None = None + @field_validator('tweetText') + def tweet_text_max_len(cls, val: LocalizedString | None) -> LocalizedString | None: + if val is None: + return None + if len(val.fi) > TWEET_TEXT_MAX_LENGTH or len(val.sv) > TWEET_TEXT_MAX_LENGTH or len(val.en) > TWEET_TEXT_MAX_LENGTH: + raise ValueError('Tweet text exceeds max length') + return val + @field_serializer("literature", "learningEnvironments") def serialize_set_as_list(self, v, _info) -> list[dict] | None: serialized_list = serialize_as_list(v) From e5e5d30fed8c59ffbfc2c084da55691f4a522724 Mon Sep 17 00:00:00 2001 From: rrimpila Date: Thu, 2 Apr 2026 15:33:18 +0300 Subject: [PATCH 3/4] LABP-277 use SIS_MAX_TWEET_LENGTH instead of own constant --- funidata_utils/schemas/sisu/course_unit_realisation.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/funidata_utils/schemas/sisu/course_unit_realisation.py b/funidata_utils/schemas/sisu/course_unit_realisation.py index c3e81e2..2ea9ca0 100644 --- a/funidata_utils/schemas/sisu/course_unit_realisation.py +++ b/funidata_utils/schemas/sisu/course_unit_realisation.py @@ -10,13 +10,10 @@ from funidata_utils.schemas.sisu.common import ( LocalDateRange, LocalDateTimeRange, OTM_ID_REGEX_VALIDATED_STR, sis_code_urn_pattern, STRIPPED_STR, IntRange, SIS_MAX_MEDIUM_SET_SIZE, OrganisationRoleShare, SIS_MAX_SMALL_SET_SIZE, - LocalizedString, + LocalizedString, SIS_MAX_TWEET_LENGTH, ) from funidata_utils.schemas.sisu.course_unit import CooperationNetworkDetails - -TWEET_TEXT_MAX_LENGTH = 160 - class LiteratureName(HashableBaseModel): localId: str type: Literal["LiteratureName"] @@ -130,7 +127,7 @@ class CourseUnitRealisation(SisBase): def tweet_text_max_len(cls, val: LocalizedString | None) -> LocalizedString | None: if val is None: return None - if len(val.fi) > TWEET_TEXT_MAX_LENGTH or len(val.sv) > TWEET_TEXT_MAX_LENGTH or len(val.en) > TWEET_TEXT_MAX_LENGTH: + if len(val.fi) > SIS_MAX_TWEET_LENGTH or len(val.sv) > SIS_MAX_TWEET_LENGTH or len(val.en) > SIS_MAX_TWEET_LENGTH: raise ValueError('Tweet text exceeds max length') return val From 1a68fc5998e89c5094dc606348d98d52cb84e187 Mon Sep 17 00:00:00 2001 From: rrimpila Date: Thu, 2 Apr 2026 17:04:50 +0300 Subject: [PATCH 4/4] LABP-277 Fix review note --- funidata_utils/schemas/sisu/course_unit_realisation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funidata_utils/schemas/sisu/course_unit_realisation.py b/funidata_utils/schemas/sisu/course_unit_realisation.py index 2ea9ca0..d2c711c 100644 --- a/funidata_utils/schemas/sisu/course_unit_realisation.py +++ b/funidata_utils/schemas/sisu/course_unit_realisation.py @@ -127,7 +127,7 @@ class CourseUnitRealisation(SisBase): def tweet_text_max_len(cls, val: LocalizedString | None) -> LocalizedString | None: if val is None: return None - if len(val.fi) > SIS_MAX_TWEET_LENGTH or len(val.sv) > SIS_MAX_TWEET_LENGTH or len(val.en) > SIS_MAX_TWEET_LENGTH: + if len(val.fi or '') > SIS_MAX_TWEET_LENGTH or len(val.sv or '') > SIS_MAX_TWEET_LENGTH or len(val.en or '') > SIS_MAX_TWEET_LENGTH: raise ValueError('Tweet text exceeds max length') return val