khard.contacts
==============

.. py:module:: khard.contacts

.. autoapi-nested-parse::

   Classes and logic to handle vCards in khard.

   This module explicitly supports the vCard specifications version 3.0 and 4.0
   which can be found here:
   - version 3.0: https://tools.ietf.org/html/rfc2426
   - version 4.0: https://tools.ietf.org/html/rfc6350

   For further details about the application and device values for the kind
   field see:
   - application: https://tools.ietf.org/html/rfc6473
   - device: https://tools.ietf.org/html/rfc6869



Attributes
----------

.. autoapisummary::

   khard.contacts.logger
   khard.contacts.T
   khard.contacts.LabeledStrs


Classes
-------

.. autoapisummary::

   khard.contacts.VCardWrapper
   khard.contacts.YAMLEditable
   khard.contacts.Contact


Functions
---------

.. autoapisummary::

   khard.contacts.multi_property_key
   khard.contacts.atomic_write


Module Contents
---------------

.. py:data:: logger

.. py:data:: T

.. py:data:: LabeledStrs

.. py:function:: multi_property_key(item: str) -> tuple[Literal[0], str]
                 multi_property_key(item: dict[T, Any]) -> tuple[Literal[1], T]
                 multi_property_key(item: Union[str, dict[T, Any]]) -> tuple[Literal[0, 1], Union[T, str]]

   Key function to pass to sorted(), allowing sorting of dicts with lists
   and strings. Dicts will be sorted by their label, after other types.

   :param item: member of the list being sorted
   :type item: a dict with a single entry or any sortable type
   :returns: a pair, the first item is int(isinstance(item, dict).
       The second is either the key from the dict or the unchanged item if it
       is not a dict.


.. py:class:: VCardWrapper(vcard: vobject.base.Component, version: Optional[str] = None)

   Wrapper class around a vobject.vCard object.

   This class can wrap a single vCard and presents its data in a manner
   suitable for khard.  Additionally some details of the vCard specifications
   in RFC 2426 (version 3.0) and RFC 6350 (version 4.0) that are not enforced
   by the vobject library are enforced here.


   .. py:attribute:: _default_version
      :value: '3.0'



   .. py:attribute:: _supported_versions
      :value: ('3.0', '4.0')



   .. py:attribute:: _default_kind
      :value: 'individual'



   .. py:attribute:: _supported_kinds
      :value: ('individual', 'group', 'org', 'location', 'application', 'device')



   .. py:attribute:: phone_types_v3
      :value: ('bbs', 'car', 'cell', 'fax', 'home', 'isdn', 'msg', 'modem', 'pager', 'pcs', 'video', 'voice', 'work')



   .. py:attribute:: email_types_v3
      :value: ('home', 'internet', 'work', 'x400')



   .. py:attribute:: address_types_v3
      :value: ('dom', 'intl', 'home', 'parcel', 'postal', 'work')



   .. py:attribute:: phone_types_v4
      :value: ('text', 'voice', 'fax', 'cell', 'video', 'pager', 'textphone', 'home', 'work')



   .. py:attribute:: email_types_v4
      :value: ('home', 'internet', 'work')



   .. py:attribute:: address_types_v4
      :value: ('home', 'work')



   .. py:attribute:: vcard


   .. py:method:: __str__() -> str


   .. py:method:: get_first(property: Literal['n']) -> Optional[vobject.vcard.Name]
                  get_first(property: Literal['adr']) -> Optional[vobject.vcard.Address]
                  get_first(property: str) -> Optional[str]

      Get a property from the underlying vCard.

      This method should only be called for properties with cardinality \*1
      (zero or one).  Otherwise only the first value will be returned. If the
      property is not present None will be retuned.

      The type annotation for the return value is str but this is not
      enforced so it is up to the caller to make sure to only call this
      method for properties where the underlying vobject library returns a
      str.

      :param property: the field value to get
      :returns: the property value or None



   .. py:method:: get_all(name: str) -> list

      Get all values of the given vCard property.

      It does not matter what the individual vcard properties store as their
      value.  This function returns them untouched inside an aggregating
      list.

      If the property is part of a group containing exactly two items, with
      exactly one ABLABEL. the property will be prefixed with that ABLABEL.

      :param name: the name of the property (should be UPPER case)
      :returns: the values from all occurrences of the named property



   .. py:method:: _delete_vcard_object(name: str) -> None

      Delete all fields with the given name from the underlying vCard.

      If a field that will be deleted is in a group with an X-ABLABEL field,
      that X-ABLABEL field will also be deleted.  These fields are commonly
      added by the Apple address book to attach custom labels to some fields.

      :param name: the name of the fields to delete



   .. py:method:: _parse_type_value(types: Sequence[str], supported_types: Sequence[str]) -> tuple[list[str], list[str], int]
      :staticmethod:


      Parse type value of phone numbers, email and post addresses.

      :param types: list of type values
      :param supported_types: all allowed standard types
      :returns: tuple of standard and custom types and pref integer



   .. py:method:: _get_types_for_vcard_object(object: vobject.base.ContentLine, default_type: str) -> list[str]

      get list of types for phone number, email or post address

      :param object: vcard class object
      :param default_type: use if the object contains no type
      :returns: list of type labels



   .. py:property:: version
      :type: Optional[str]



   .. py:property:: uid
      :type: Optional[str]



   .. py:method:: _update_revision() -> None

      Generate a new REV field for the vCard, replace any existing

      All vCards should only always have one revision, this is a
      requirement for version 4 but also makes sense for all other
      versions.



   .. py:property:: birthday
      :type: Optional[khard.helpers.typing.Date]


      Return the birthday as a datetime object or a string depending on
      whether it is of type text or not.  If no birthday is present in the
      vcard None is returned.

      :returns: contacts birthday or None if not available



   .. py:property:: anniversary
      :type: Optional[khard.helpers.typing.Date]


      :returns: contacts anniversary or None if not available



   .. py:method:: _get_ablabel(item: vobject.base.ContentLine) -> str

      Get an ABLABEL for a specified item in the vCard.
      Will return the ABLABEL only if the item is part of a group with
      exactly two items, exactly one of which is an ABLABEL.

      :param item: the item to be labelled
      :returns: the ABLABEL in the circumstances above or an empty string



   .. py:method:: _get_new_group(group_type: str = '') -> str

      Get an unused group name for adding new groups. Uses the form
      item123 or itemgroup_type123 if a grouptype is specified.

      :param group_type: (Optional) a string to add between "item" and
          the number
      :returns: the name of the first unused group of the specified form



   .. py:method:: _add_labelled_property(property: str, value: khard.helpers.typing.StrList, label: Optional[str] = None, name_groups: bool = False, allowed_object_type: Union[None, type[str], type[list]] = str) -> None

      Add an object to the VCARD. If a label is given it will be added to
      a group with an ABLABEL.

      :param property: type of object to add to the VCARD.
      :param value: Contents of the object to add.
      :param label: an optional label to add to the property
      :param name_groups: (Optional) If True, use the property in the
          group name for labelled objects.
      :param allowed_object_type: (Optional) set the accepted return type
          for vcard attribute



   .. py:method:: _prepare_birthday_value(date: khard.helpers.typing.Date) -> tuple[Optional[str], bool]

      Prepare a value to be stored in a BDAY or ANNIVERSARY attribute.

      :param date: the date like value to be stored
      :returns: the object to set as the .value for the attribute and whether
          it should be stored as plain text



   .. py:property:: kind
      :type: str



   .. py:method:: _kind_attribute_name() -> str


   .. py:property:: formatted_name
      :type: str



   .. py:method:: _get_names_part(part: str) -> list[str]

      Get some part of the "N" entry in the vCard as a list

      :param part: the name to get e.g. "prefix" or "given"
      :returns: a list of entries for this name part



   .. py:method:: _get_name_prefixes() -> list[str]


   .. py:method:: _get_first_names() -> list[str]


   .. py:method:: _get_additional_names() -> list[str]


   .. py:method:: _get_last_names() -> list[str]


   .. py:method:: _get_name_suffixes() -> list[str]


   .. py:method:: get_first_name_last_name() -> str

      Compute the full name of the contact by joining first, additional
      and last names together



   .. py:method:: get_last_name_first_name() -> str

      Compute the full name of the contact by joining the last names and
      then after a comma the first and additional names together



   .. py:property:: first_name
      :type: Optional[str]



   .. py:property:: last_name
      :type: Optional[str]



   .. py:method:: _add_name(prefix: khard.helpers.typing.StrList, first_name: khard.helpers.typing.StrList, additional_name: khard.helpers.typing.StrList, last_name: khard.helpers.typing.StrList, suffix: khard.helpers.typing.StrList) -> None

      Add an N entry to the vCard. No old entries are affected.

      :param prefix:
      :param first_name:
      :param additional_name:
      :param last_name:
      :param suffix:



   .. py:property:: organisations
      :type: list[Union[list[str], dict[str, list[str]]]]


      :returns: list of organisations, sorted alphabetically



   .. py:method:: _add_organisation(organisation: khard.helpers.typing.StrList, label: Optional[str] = None) -> None

      Add one ORG entry to the underlying vcard

      :param organisation: the value to add
      :param label: an optional label to add



   .. py:property:: titles
      :type: LabeledStrs



   .. py:method:: _add_title(title: str, label: Optional[str] = None) -> None


   .. py:property:: roles
      :type: LabeledStrs



   .. py:method:: _add_role(role: str, label: Optional[str] = None) -> None


   .. py:property:: nicknames
      :type: LabeledStrs



   .. py:method:: _add_nickname(nickname: str, label: Optional[str] = None) -> None


   .. py:property:: notes
      :type: LabeledStrs



   .. py:method:: _add_note(note: str, label: Optional[str] = None) -> None


   .. py:property:: webpages
      :type: LabeledStrs



   .. py:method:: _add_webpage(webpage: str, label: Optional[str] = None) -> None


   .. py:property:: categories
      :type: Union[list[str], list[list[str]]]



   .. py:method:: _add_category(categories: list[str]) -> None

      Add categories to the vCard

      :param categories:



   .. py:property:: phone_numbers
      :type: dict[str, list[str]]


      :returns: dict of type and phone number list



   .. py:method:: _add_phone_number(type: str, number: str) -> None


   .. py:property:: emails
      :type: dict[str, list[str]]


      :returns: dict of type and email address list



   .. py:method:: add_email(type: str, address: str) -> None


   .. py:property:: post_addresses
      :type: dict[str, list[khard.helpers.typing.PostAddress]]


      :returns: dict of type and post address list



   .. py:method:: get_formatted_post_addresses() -> dict[str, list[str]]


   .. py:method:: _add_post_address(type: str, box: khard.helpers.typing.StrList, extended: khard.helpers.typing.StrList, street: khard.helpers.typing.StrList, code: khard.helpers.typing.StrList, city: khard.helpers.typing.StrList, region: khard.helpers.typing.StrList, country: khard.helpers.typing.StrList) -> None


.. py:class:: YAMLEditable(vcard: vobject.base.Component, supported_private_objects: Optional[list[str]] = None, version: Optional[str] = None, localize_dates: bool = False)

   Bases: :py:obj:`VCardWrapper`


   Conversion of vcards to YAML and updating the vcard from YAML


   .. py:attribute:: supported_private_objects
      :value: []



   .. py:attribute:: localize_dates
      :value: False



   .. py:method:: _get_private_objects() -> dict[str, LabeledStrs]


   .. py:method:: _add_private_object(key: str, value: str) -> None


   .. py:method:: get_formatted_anniversary() -> str


   .. py:method:: get_formatted_birthday() -> str


   .. py:method:: _format_date_object(date: Optional[khard.helpers.typing.Date], localize: bool) -> str
      :staticmethod:



   .. py:method:: _filter_invalid_tags(contents: str) -> str
      :staticmethod:



   .. py:method:: _parse_yaml(input: str) -> dict
      :staticmethod:


      Parse a YAML document into a dictionary and validate the data to
      some degree.

      :param input: the YAML document to parse
      :returns: the parsed data structure



   .. py:method:: _set_string_list(setter: Callable[[str, Optional[str]], None], key: str, data: dict[str, Union[str, list[str]]]) -> None
      :staticmethod:


      Pre-process a string or list and set each value with the given
      setter

      :param setter: the setter method to add a value to a card
      :param key:
      :param data:



   .. py:method:: _set_date(target: str, key: str, data: dict) -> None


   .. py:method:: update(input: str) -> None

      Update this vcard with some yaml input

      :param input: a yaml string to parse and then use to update self



   .. py:method:: to_yaml() -> str

      Convert this contact to a YAML string

      The conversion follows the implicit schema that is given by the
      internal YAML template of khard.

      :returns: a YAML representation of this contact



.. py:function:: atomic_write(dest: str, overwrite: bool = False) -> Iterator[IO[str]]

   Atomically write to the destination file.

   Optionally overwrite the path (using rename) rather than using `os.link`.


.. py:class:: Contact(vcard: vobject.base.Component, address_book: Contact.__init__.address_book, filename: str, supported_private_objects: Optional[list[str]] = None, vcard_version: Optional[str] = None, localize_dates: bool = False)

   Bases: :py:obj:`YAMLEditable`


   Conversion of vcards to YAML and updating the vcard from YAML


   .. py:attribute:: address_book


   .. py:attribute:: filename


   .. py:method:: new(address_book: Contact.new.address_book, supported_private_objects: Optional[list[str]] = None, version: Optional[str] = None, localize_dates: bool = False) -> Contact
      :classmethod:


      Create a new Contact from scratch



   .. py:method:: from_file(address_book: Contact.from_file.address_book, filename: str, query: khard.query.Query = AnyQuery(), supported_private_objects: Optional[list[str]] = None, localize_dates: bool = False) -> Optional[Contact]
      :classmethod:


      Load a Contact object from a .vcf file if the plain file
      matches the query.

      :param address_book: the address book where this contact is stored
      :param filename: the file name of the .vcf file
      :param query: the query to search in the source file or None to load
          the file unconditionally
      :param supported_private_objects: the list of private property names
          that will be loaded from the actual vcard and represented in this
          object
      :param localize_dates: should the formatted output of anniversary
          and birthday be localized or should the iso format be used instead
      :returns: the loaded Contact or None if the file didn't match



   .. py:method:: from_yaml(address_book: Contact.from_yaml.address_book, yaml: str, supported_private_objects: Optional[list[str]] = None, version: Optional[str] = None, localize_dates: bool = False) -> Contact
      :classmethod:


      Use this if you want to create a new contact from user input.



   .. py:method:: clone_with_yaml_update(contact: Contact, yaml: str, localize_dates: bool = False) -> Contact
      :classmethod:


      Use this if you want to clone an existing contact and replace its data
      with new user input in one step.



   .. py:method:: __eq__(other: object) -> bool


   .. py:method:: __ne__(other: object) -> bool


   .. py:method:: pretty(verbose: bool = True) -> str


   .. py:method:: write_to_file(overwrite: bool = False) -> None


   .. py:method:: delete_vcard_file() -> None


   .. py:method:: get_properties() -> list[str]
      :classmethod:


      Return the property names that are defined on this class.



