Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RELATED-TO support #243

Merged
merged 1 commit into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions ical/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
RecurrenceId,
RequestStatus,
Uri,
RelatedTo,
)
from .util import dtstamp_factory, normalize_datetime, uid_factory

Expand Down Expand Up @@ -166,9 +167,12 @@ class Event(ComponentModel):
instance within the recurrence set.
"""

related: list[str] = Field(default_factory=list)
related_to: list[RelatedTo] = Field(alias="related-to", default_factory=list)
"""Used to represent a relationship or reference between events."""

related: list[str] = Field(default_factory=list)
"""Unused and will be deleted in a future release"""

resources: list[str] = Field(default_factory=list)
"""Defines the equipment or resources anticipated for the calendar event."""

Expand Down Expand Up @@ -438,4 +442,6 @@ def _validate_duration_unit(cls, values: dict[str, Any]) -> dict[str, Any]:
return values

_validate_until_dtstart = root_validator(allow_reuse=True)(validate_until_dtstart)
_validate_recurrence_dates = root_validator(allow_reuse=True)(validate_recurrence_dates)
_validate_recurrence_dates = root_validator(allow_reuse=True)(
validate_recurrence_dates
)
6 changes: 5 additions & 1 deletion ical/journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from .component import ComponentModel, validate_until_dtstart, validate_recurrence_dates
from .parsing.property import ParsedProperty
from .types import CalAddress, Classification, Recur, RecurrenceId, RequestStatus, Uri
from .types import CalAddress, Classification, Recur, RecurrenceId, RequestStatus, Uri, RelatedTo
from .util import dtstamp_factory, normalize_datetime, uid_factory

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,6 +63,10 @@ class Journal(ComponentModel):
)
organizer: Optional[CalAddress] = None
recurrence_id: Optional[RecurrenceId] = Field(alias="recurrence-id")

related_to: list[RelatedTo] = Field(alias="related-to", default_factory=list)
"""Used to represent a relationship or reference between events."""

related: list[str] = Field(default_factory=list)
rrule: Optional[Recur] = None
rdate: list[Union[datetime.datetime, datetime.date]] = Field(default_factory=list)
Expand Down
5 changes: 5 additions & 0 deletions ical/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
RecurrenceId,
RequestStatus,
Uri,
RelatedTo,
)
from .util import dtstamp_factory, normalize_datetime, uid_factory

Expand Down Expand Up @@ -68,6 +69,10 @@ class Todo(ComponentModel):
percent: Optional[int] = None
priority: Optional[Priority] = None
recurrence_id: Optional[RecurrenceId] = Field(alias="recurrence-id")

related_to: list[RelatedTo] = Field(alias="related-to", default_factory=list)
"""Used to represent a relationship or reference between events."""

request_status: Optional[RequestStatus] = Field(
alias="request-status",
default_value=None,
Expand Down
3 changes: 3 additions & 0 deletions ical/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .period import FreeBusyType, Period
from .priority import Priority
from .recur import Frequency, Range, Recur, RecurrenceId, Weekday, WeekdayValue
from .relation import RelatedTo, RelationshipType
from .request_status import RequestStatus
from .uri import Uri
from .utc_offset import UtcOffset
Expand All @@ -25,6 +26,8 @@
"Range",
"Recur",
"RecurrenceId",
"RelatedTo",
"RelationshipType",
"RequestStatus",
"UtcOffset",
"Uri",
Expand Down
5 changes: 1 addition & 4 deletions ical/types/cal_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ class Role(str, enum.Enum):

@DATA_TYPE.register("CAL-ADDRESS")
class CalAddress(BaseModel):
"""A value type for a property that contains a calendar user address.
This is a subclass of string so that it can be used in place of a string
to get the calendar address, but also supports additional properties.
"""
"""A value type for a property that contains a calendar user address."""

uri: Uri = Field(alias="value")
"""The calendar user address as a uri."""
Expand Down
69 changes: 69 additions & 0 deletions ical/types/relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Implementation of the RELATED-TO property."""

import enum
from dataclasses import dataclass
from typing import Any
import logging

try:
from pydantic.v1 import root_validator
except ImportError:
from pydantic import root_validator

from .data_types import DATA_TYPE
from ical.parsing.property import ParsedProperty, ParsedPropertyParameter
from .parsing import parse_parameter_values


class RelationshipType(str, enum.Enum):
"""Type of hierarchical relationship associated with the calendar component."""

PARENT = "PARENT"
"""Parent relationship - Default."""

CHILD = "CHILD"
"""Child relationship."""

SIBBLING = "SIBBLING"
"""Sibling relationship."""


@DATA_TYPE.register("RELATED-TO")
@dataclass
class RelatedTo:
"""Used to represent a relationship or reference between one calendar component and another."""

uid: str
"""The value of the related-to property is the persistent, globally unique identifier of another calendar component."""

reltype: RelationshipType = RelationshipType.PARENT
"""Indicate the type of hierarchical relationship associated with the calendar component specified by the uid."""

@classmethod
def __parse_property_value__(cls, prop: Any) -> int:
"""Parse a rfc5545 int value."""
logging.info("prop=%s", prop)
if isinstance(prop, ParsedProperty):
data = {"uid": prop.value}
for param in prop.params or ():
if len(param.values) > 1:
raise ValueError("Expected only one value for RELATED-TO parameter")
data[param.name] = param.values[0]
return data
return {"uid": prop}

_parse_parameter_values = root_validator(pre=True, allow_reuse=True)(
parse_parameter_values
)

@classmethod
def __encode_property_value__(cls, model_data: dict[str, str]) -> str | None:
return model_data.pop("uid")

@classmethod
def __encode_property_params__(
cls, model_data: dict[str, Any]
) -> list[ParsedPropertyParameter]:
if "reltype" not in model_data:
return []
return [ParsedPropertyParameter(name="RELTYPE", values=[model_data["reltype"]])]
64 changes: 64 additions & 0 deletions tests/testdata/related_to.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
input: |-
BEGIN:VCALENDAR
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
VERSION:2.0
BEGIN:VTODO
UID:20070313T123432Z-456553@example.com
DTSTAMP:20070313T123432Z
DUE;VALUE=DATE:20070501
SUMMARY:Submit Quebec Income Tax Return for 2006
CLASS:CONFIDENTIAL
CATEGORIES:FAMILY,FINANCE
STATUS:NEEDS-ACTION
END:VTODO
BEGIN:VTODO
UID:20070313T123432Z-456554@example.com
DTSTAMP:20070313T123432Z
SUMMARY:Buy pens
STATUS:NEEDS-ACTION
RELATED-TO;RELTYPE=PARENT:20070313T123432Z-456553@example.com
END:VTODO
END:VCALENDAR
output:
calendars:
- prodid: -//hacksw/handcal//NONSGML v1.0//EN
version: '2.0'
todos:
- dtstamp: '2007-03-13T12:34:32+00:00'
uid: 20070313T123432Z-456553@example.com
due: '2007-05-01'
status: NEEDS-ACTION
classification: CONFIDENTIAL
categories:
- FAMILY
- FINANCE
summary: Submit Quebec Income Tax Return for 2006
- dtstamp: '2007-03-13T12:34:32+00:00'
uid: 20070313T123432Z-456554@example.com
status: NEEDS-ACTION
summary: Buy pens
related_to:
- uid: 20070313T123432Z-456553@example.com
reltype: PARENT
encoded: |-
BEGIN:VCALENDAR
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
VERSION:2.0
BEGIN:VTODO
DTSTAMP:20070313T123432Z
UID:20070313T123432Z-456553@example.com
CATEGORIES:FAMILY
CATEGORIES:FINANCE
CLASS:CONFIDENTIAL
DUE:20070501
STATUS:NEEDS-ACTION
SUMMARY:Submit Quebec Income Tax Return for 2006
END:VTODO
BEGIN:VTODO
DTSTAMP:20070313T123432Z
UID:20070313T123432Z-456554@example.com
RELATED-TO;RELTYPE=PARENT:20070313T123432Z-456553@example.com
STATUS:NEEDS-ACTION
SUMMARY:Buy pens
END:VTODO
END:VCALENDAR
64 changes: 64 additions & 0 deletions tests/testdata/related_to_default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
input: |-
BEGIN:VCALENDAR
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
VERSION:2.0
BEGIN:VTODO
UID:20070313T123432Z-456553@example.com
DTSTAMP:20070313T123432Z
DUE;VALUE=DATE:20070501
SUMMARY:Submit Quebec Income Tax Return for 2006
CLASS:CONFIDENTIAL
CATEGORIES:FAMILY,FINANCE
STATUS:NEEDS-ACTION
END:VTODO
BEGIN:VTODO
UID:20070313T123432Z-456554@example.com
DTSTAMP:20070313T123432Z
SUMMARY:Buy pens
STATUS:NEEDS-ACTION
RELATED-TO:20070313T123432Z-456553@example.com
END:VTODO
END:VCALENDAR
output:
calendars:
- prodid: -//hacksw/handcal//NONSGML v1.0//EN
version: '2.0'
todos:
- dtstamp: '2007-03-13T12:34:32+00:00'
uid: 20070313T123432Z-456553@example.com
due: '2007-05-01'
status: NEEDS-ACTION
classification: CONFIDENTIAL
categories:
- FAMILY
- FINANCE
summary: Submit Quebec Income Tax Return for 2006
- dtstamp: '2007-03-13T12:34:32+00:00'
uid: 20070313T123432Z-456554@example.com
status: NEEDS-ACTION
summary: Buy pens
related_to:
- uid: 20070313T123432Z-456553@example.com
reltype: PARENT
encoded: |-
BEGIN:VCALENDAR
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
VERSION:2.0
BEGIN:VTODO
DTSTAMP:20070313T123432Z
UID:20070313T123432Z-456553@example.com
CATEGORIES:FAMILY
CATEGORIES:FINANCE
CLASS:CONFIDENTIAL
DUE:20070501
STATUS:NEEDS-ACTION
SUMMARY:Submit Quebec Income Tax Return for 2006
END:VTODO
BEGIN:VTODO
DTSTAMP:20070313T123432Z
UID:20070313T123432Z-456554@example.com
RELATED-TO;RELTYPE=PARENT:20070313T123432Z-456553@example.com
STATUS:NEEDS-ACTION
SUMMARY:Buy pens
END:VTODO
END:VCALENDAR
Loading