"""CAL-ADDRESS values from :rfc:`5545`."""
from typing import Any, ClassVar
from icalendar.compatibility import Self
from icalendar.error import JCalParsingError
from icalendar.parser import Parameters
from icalendar.parser_tools import DEFAULT_ENCODING, to_unicode
[docs]
class vCalAddress(str):
r"""Calendar User Address
Value Name:
CAL-ADDRESS
Purpose:
This value type is used to identify properties that contain a
calendar user address.
Description:
The value is a URI as defined by [RFC3986] or any other
IANA-registered form for a URI. When used to address an Internet
email transport address for a calendar user, the value MUST be a
mailto URI, as defined by [RFC2368].
Example:
``mailto:`` is in front of the address.
.. code-block:: text
mailto:jane_doe@example.com
Parsing:
.. code-block:: pycon
>>> from icalendar import vCalAddress
>>> cal_address = vCalAddress.from_ical('mailto:jane_doe@example.com')
>>> cal_address
vCalAddress('mailto:jane_doe@example.com')
Encoding:
.. code-block:: pycon
>>> from icalendar import vCalAddress, Event
>>> event = Event()
>>> jane = vCalAddress("mailto:jane_doe@example.com")
>>> jane.name = "Jane"
>>> event["organizer"] = jane
>>> print(event.to_ical().decode().replace('\\r\\n', '\\n').strip())
BEGIN:VEVENT
ORGANIZER;CN=Jane:mailto:jane_doe@example.com
END:VEVENT
"""
default_value: ClassVar[str] = "CAL-ADDRESS"
params: Parameters
__slots__ = ("params",)
def __new__(
cls,
value,
encoding=DEFAULT_ENCODING,
/,
params: dict[str, Any] | None = None,
):
value = to_unicode(value, encoding=encoding)
self = super().__new__(cls, value)
self.params = Parameters(params)
return self
def __repr__(self):
return f"vCalAddress('{self}')"
[docs]
def to_ical(self):
return self.encode(DEFAULT_ENCODING)
[docs]
@classmethod
def from_ical(cls, ical) -> Self:
return cls(ical)
@property
def ical_value(self):
"""The ``mailto:`` part of the address."""
return str(self)
@property
def email(self) -> str:
"""The email address without ``mailto:`` at the start."""
if self.lower().startswith("mailto:"):
return self[7:]
return str(self)
from icalendar.param import (
CN,
CUTYPE,
DELEGATED_FROM,
DELEGATED_TO,
DIR,
LANGUAGE,
PARTSTAT,
ROLE,
RSVP,
SENT_BY,
VALUE,
)
name = CN
@staticmethod
def _get_email(email: str) -> str:
"""Extract email and add mailto: prefix if needed.
Handles case-insensitive mailto: prefix checking.
Parameters:
email: Email string that may or may not have mailto: prefix
Returns:
Email string with mailto: prefix
"""
if not email.lower().startswith("mailto:"):
return f"mailto:{email}"
return email
[docs]
@classmethod
def new(
cls,
email: str,
/,
cn: str | None = None,
cutype: str | None = None,
delegated_from: str | None = None,
delegated_to: str | None = None,
directory: str | None = None,
language: str | None = None,
partstat: str | None = None,
role: str | None = None,
rsvp: bool | None = None, # noqa: FBT001, RUF100
sent_by: str | None = None,
):
"""Create a new vCalAddress with RFC 5545 parameters.
Creates a vCalAddress instance with automatic mailto: prefix handling
and support for all standard RFC 5545 parameters.
Parameters:
email: The email address (mailto: prefix added automatically if missing)
cn: Common Name parameter
cutype: Calendar user type (INDIVIDUAL, GROUP, RESOURCE, ROOM)
delegated_from: Email of the calendar user that delegated
delegated_to: Email of the calendar user that was delegated to
directory: Reference to directory information
language: Language for text values
partstat: Participation status (NEEDS-ACTION, ACCEPTED, DECLINED, etc.)
role: Role (REQ-PARTICIPANT, OPT-PARTICIPANT, NON-PARTICIPANT, CHAIR)
rsvp: Whether RSVP is requested
sent_by: Email of the calendar user acting on behalf of this user
Returns:
vCalAddress: A new calendar address with specified parameters
Raises:
TypeError: If email is not a string
Examples:
Basic usage:
>>> from icalendar.prop import vCalAddress
>>> addr = vCalAddress.new("test@test.com")
>>> str(addr)
'mailto:test@test.com'
With parameters:
>>> addr = vCalAddress.new("test@test.com", cn="Test User", role="CHAIR")
>>> addr.params["CN"]
'Test User'
>>> addr.params["ROLE"]
'CHAIR'
"""
if not isinstance(email, str):
raise TypeError(f"Email must be a string, not {type(email).__name__}")
# Handle mailto: prefix (case-insensitive)
email_with_prefix = cls._get_email(email)
# Create the address
addr = cls(email_with_prefix)
# Set parameters if provided
if cn is not None:
addr.params["CN"] = cn
if cutype is not None:
addr.params["CUTYPE"] = cutype
if delegated_from is not None:
addr.params["DELEGATED-FROM"] = cls._get_email(delegated_from)
if delegated_to is not None:
addr.params["DELEGATED-TO"] = cls._get_email(delegated_to)
if directory is not None:
addr.params["DIR"] = directory
if language is not None:
addr.params["LANGUAGE"] = language
if partstat is not None:
addr.params["PARTSTAT"] = partstat
if role is not None:
addr.params["ROLE"] = role
if rsvp is not None:
addr.params["RSVP"] = "TRUE" if rsvp else "FALSE"
if sent_by is not None:
addr.params["SENT-BY"] = cls._get_email(sent_by)
return addr
[docs]
def to_jcal(self, name: str) -> list:
"""Return this property in jCal format."""
return [name, self.params.to_jcal(), self.VALUE.lower(), self.ical_value]
[docs]
@classmethod
def examples(cls) -> list[Self]:
"""Examples of vCalAddress."""
return [cls.new("you@example.org", cn="You There")]
[docs]
@classmethod
def from_jcal(cls, jcal_property: list) -> Self:
"""Parse jCal from :rfc:`7265`.
Parameters:
jcal_property: The jCal property to parse.
Raises:
~error.JCalParsingError: If the provided jCal is invalid.
"""
JCalParsingError.validate_property(jcal_property, cls)
JCalParsingError.validate_value_type(jcal_property[3], str, cls, 3)
return cls(
jcal_property[3],
params=Parameters.from_jcal_property(jcal_property),
)
__all__ = ["vCalAddress"]