Exemple #1
0
def initialize_converters():
    # order is very important here:
    # 1) all simple value type converters
    import ics.valuetype.base
    import ics.valuetype.generic
    import ics.valuetype.text
    import ics.valuetype.datetime
    import ics.valuetype.special

    # 2) all relatively simple attribute converters and advanced component converters
    import ics.converter.base
    import ics.converter.value
    import ics.converter.types.timespan
    import ics.converter.types.various

    # 3) converters for all remaining component subclasses
    from ics.converter.component import ComponentMeta
    import ics.converter.types.timezone  # vTimezone is a Component
    ComponentMeta.BY_TYPE[Event] = ComponentMeta(Event)
    ComponentMeta.BY_TYPE[Todo] = ComponentMeta(Todo)

    # 4) the converter for the calendar
    import ics.converter.types.calendar

    global initialize_converters
    initialize_converters = lambda: None
Exemple #2
0
class Event(EventAttrs):
    """A calendar event.

    Can be full-day or between two instants.
    Can be defined by a beginning instant and
    a duration *or* end instant.

    Unsupported event attributes can be found in `event.extra`,
    a :class:`ics.parse.Container`. You may add some by appending a
    :class:`ics.parse.ContentLine` to `.extra`
    """

    _timespan: EventTimespan = attr.ib(validator=instance_of(EventTimespan))

    Meta = ComponentMeta("VEVENT")

    def __init__(
            self,
            summary: str = None,
            begin: DatetimeLike = None,
            end: DatetimeLike = None,
            duration: TimedeltaLike = None,
            *args, **kwargs
    ):
        """Initializes a new :class:`ics.event.Event`.

        Raises:
            ValueError: if `timespan` and any of `begin`, `end` or `duration`
             are specified at the same time,
             or if validation of the timespan fails (see :method:`ics.timespan.Timespan.validate`).
        """
        if (begin is not None or end is not None or duration is not None) and "timespan" in kwargs:
            raise ValueError("can't specify explicit timespan together with any of begin, end or duration")
        kwargs.setdefault("timespan", EventTimespan(ensure_datetime(begin), ensure_datetime(end), ensure_timedelta(duration)))
        super(Event, self).__init__(kwargs.pop("timespan"), summary, *args, **kwargs)
Exemple #3
0
class Attendee(Person):
    rsvp: Optional[bool] = attr.ib(default=None)
    role: Optional[str] = attr.ib(default=None)
    partstat: Optional[str] = attr.ib(default=None)
    cutype: Optional[str] = attr.ib(default=None)

    Meta = ComponentMeta("ATTENDEE")
Exemple #4
0
class Person(object):
    email: str = attr.ib()
    common_name: str = attr.ib(default=None)
    dir: Optional[str] = attr.ib(default=None)
    sent_by: Optional[str] = attr.ib(default=None)
    extra: Dict[str, List[str]] = attr.ib(factory=dict)

    Meta = ComponentMeta("ABSTRACT-PERSON")
Exemple #5
0
class Todo(TodoAttrs):
    """A todo list entry.

    Can have a start time and duration, or start and due time,
    or only start or due time.
    """
    _timespan: TodoTimespan = attr.ib(validator=instance_of(TodoTimespan))

    Meta = ComponentMeta("VTODO")

    def __init__(self,
                 begin: DatetimeLike = None,
                 due: DatetimeLike = None,
                 duration: TimedeltaLike = None,
                 *args,
                 **kwargs):
        if (begin is not None or due is not None
                or duration is not None) and "timespan" in kwargs:
            raise ValueError(
                "can't specify explicit timespan together with any of begin, due or duration"
            )
        kwargs.setdefault(
            "timespan",
            TodoTimespan(ensure_datetime(begin), ensure_datetime(due),
                         ensure_timedelta(duration)))
        super(Todo, self).__init__(kwargs.pop("timespan"), *args, **kwargs)

    ####################################################################################################################

    def convert_due(self, representation):
        if representation == "due":
            representation = "end"
        super(Todo, self).convert_end(representation)

    due = property(TodoAttrs.end.fget, TodoAttrs.end.fset)
    # convert_due = TodoAttrs.convert_end  # see above
    due_representation = property(TodoAttrs.end_representation.fget)
    has_explicit_due = property(TodoAttrs.has_explicit_end.fget)
    due_within = TodoAttrs.ends_within

    end = property(deprecated_due(TodoAttrs.end.fget),
                   deprecated_due(TodoAttrs.end.fset))
    convert_end = deprecated_due(TodoAttrs.convert_end)
    end_representation = property(
        deprecated_due(TodoAttrs.end_representation.fget))
    has_explicit_end = property(deprecated_due(
        TodoAttrs.has_explicit_end.fget))
    ends_within = deprecated_due(TodoAttrs.ends_within)
Exemple #6
0
class BaseAlarm(Component, metaclass=ABCMeta):
    """
    A calendar event VALARM base class
    """
    Meta = ComponentMeta("VALARM", converter_class=AlarmConverter)

    trigger: Union[timedelta, datetime, None] = attr.ib(
        default=None, validator=v_optional(instance_of(
            (timedelta, datetime))))  # TODO is this relative to begin or end?
    repeat: int = attr.ib(default=None, validator=call_validate_on_inst)
    duration: timedelta = attr.ib(
        default=None,
        converter=c_optional(ensure_timedelta),
        validator=call_validate_on_inst)  # type: ignore

    # FIXME: `attach` can be specified multiple times in a "VEVENT", "VTODO", "VJOURNAL", or "VALARM" calendar component
    #  with the exception of AUDIO alarm that only allows this property to occur once.
    #  (This property is used in "VALARM" calendar components to specify an audio sound resource or an email message attachment.)

    def validate(self, attr=None, value=None):
        if self.repeat is not None:
            if self.repeat < 0:
                raise ValueError("Repeat must be great than or equal to 0.")
            if self.duration is None:
                raise ValueError(
                    "A definition of an alarm with a repeating trigger MUST include both the DURATION and REPEAT properties."
                )

        if self.duration is not None and self.duration.total_seconds() < 0:
            raise ValueError("Alarm duration timespan must be positive.")

    @property
    @abstractmethod
    def action(self):
        """ VALARM action to be implemented by concrete classes """
        ...
Exemple #7
0
class Component(RuntimeAttrValidation):
    Meta: ClassVar[ComponentMeta] = ComponentMeta("ABSTRACT-COMPONENT")

    extra: Container = attr.ib(init=False,
                               default=PLACEHOLDER_CONTAINER,
                               validator=instance_of(Container),
                               metadata={"ics_ignore": True})
    extra_params: ComponentExtraParams = attr.ib(init=False,
                                                 factory=dict,
                                                 validator=instance_of(dict),
                                                 metadata={"ics_ignore": True})

    def __attrs_post_init__(self):
        super(Component, self).__attrs_post_init__()
        if self.extra is PLACEHOLDER_CONTAINER:
            self.extra = Container(self.Meta.container_name)

    def __init_subclass__(cls):
        super().__init_subclass__()
        cls.Meta.inflate(cls)

    @classmethod
    def from_container(cls: Type[ComponentType],
                       container: Container) -> ComponentType:
        return cls.Meta.load_instance(container)

    def populate(self, container: Container):
        self.Meta.populate_instance(self, container)

    def to_container(self) -> Container:
        return self.Meta.serialize_toplevel(self)

    def serialize(self) -> str:
        return self.to_container().serialize()

    def strip_extras(self,
                     all_extras=False,
                     extra_properties=None,
                     extra_params=None,
                     property_merging=None):
        if extra_properties is None:
            extra_properties = all_extras
        if extra_params is None:
            extra_params = all_extras
        if property_merging is None:
            property_merging = all_extras
        if not any([extra_properties, extra_params, property_merging]):
            raise ValueError("need to strip at least one thing")
        if extra_properties:
            self.extra.clear()
        if extra_params:
            self.extra_params.clear()
        elif property_merging:
            for val in self.extra_params.values():
                if not isinstance(val, list): continue
                for v in val:
                    v.pop("__merge_next", None)

    def clone(self):
        """Returns an exact (shallow) copy of self"""
        # TODO deep copies?
        return attr.evolve(self)
Exemple #8
0
class Calendar(CalendarAttrs):
    """
    Represents an unique RFC 5545 iCalendar.

    Attributes:

        events: a list of `Event`s contained in the Calendar
        todos: a list of `Todo`s contained in the Calendar
        timeline: a `Timeline` instance for iterating this Calendar in chronological order

    """

    Meta = ComponentMeta("VCALENDAR")
    DEFAULT_VERSION: ClassVar[str] = "2.0"
    DEFAULT_PRODID: ClassVar[str] = "ics.py - http://git.io/lLljaA"

    def __init__(self,
                 imports: Union[str, Container, None] = None,
                 events: Optional[Iterable[Event]] = None,
                 todos: Optional[Iterable[Todo]] = None,
                 creator: str = None,
                 **kwargs):
        """Initializes a new Calendar.

        Args:
            imports (**str**): data to be imported into the Calendar,
            events (**Iterable[Event]**): `Event`s to be added to the calendar
            todos (**Iterable[Todo]**): `Todo`s to be added to the calendar
            creator (**string**): uid of the creator program.
        """
        if events is None:
            events = tuple()
        if todos is None:
            todos = tuple()
        kwargs.setdefault("version", self.DEFAULT_VERSION)
        kwargs.setdefault(
            "prodid", creator if creator is not None else self.DEFAULT_PRODID)
        super(Calendar, self).__init__(events=events, todos=todos,
                                       **kwargs)  # type: ignore
        self.timeline = Timeline(self, None)

        if imports is not None:
            if isinstance(imports, Container):
                self.populate(imports)
            else:
                containers = iter(string_to_containers(imports))
                try:
                    container = next(containers)
                    if not isinstance(container, Container):
                        raise ValueError("can't populate from %s" %
                                         type(container))
                    self.populate(container)
                except StopIteration:
                    raise ValueError("string didn't contain any ics data")
                try:
                    next(containers)
                    raise ValueError(
                        "Multiple calendars in one file are not supported by this method."
                        "Use ics.Calendar.parse_multiple()")
                except StopIteration:
                    pass

    @property
    def creator(self) -> str:
        return self.prodid

    @creator.setter
    def creator(self, value: str):
        self.prodid = value

    @classmethod
    def parse_multiple(cls, string):
        """"
        Parses an input string that may contain mutiple calendars
        and retruns a list of :class:`ics.event.Calendar`
        """
        containers = string_to_containers(string)
        return [cls(imports=c) for c in containers]

    def __str__(self) -> str:
        return "<Calendar with {} event{} and {} todo{}>".format(
            len(self.events), "s" if len(self.events) > 1 else "",
            len(self.todos), "s" if len(self.todos) > 1 else "")

    def __iter__(self) -> Iterator[str]:
        """Returns:
        iterable: an iterable version of __str__, line per line
        (with line-endings).

        Example:
            Can be used to write calendar to a file:

            >>> c = Calendar(); c.events.append(Event(summary="My cool event"))
            >>> open('my.ics', 'w').writelines(c)
        """
        return iter(self.serialize().splitlines(keepends=True))
Exemple #9
0
class Organizer(Person):
    Meta = ComponentMeta("ORGANIZER")