vObject¶
Introduction¶
vobject is a pure-Python package for generating and parsing vCard and iCalendar (aka vCalendar) objects, used for sharing and storage of personal contacts and calendar events.
It supports Python 3.8 or later. Releases in the 0.9.x series support Python 2.7, and earlier releases of Python 3.
This document gives an overview of the vCard and iCalendar standards, sufficient to begin using the package to generate or parse those that you will likely encounter in general use. And it explains the API, with examples of common tasks.
Quick Start¶
Install vobject from PyPI using pip, usually into a suitable
virtual environment:
1pip install vobject
In all code examples in this chapter, we assume that the package has been imported like this:
1import vobject
You can parse an existing contact (typically .vcf) or calendar
(typically .ics) file, and get an iterator to the contained
objects:
1with open("my-cards-file.vcf") as vo_stream:
2 for vo in vobject.readComponents(vo_stream):
3 vo.prettyPrint()
If you only want to read a single object, you can use readOne()
rather than readComponents().
Given a Python instance of a vObject, you can then perform many operations on it.
You can get its name:
1>>> print(item.name)
2VCARD
3>>>
Get its children (if any):
1>>> list(item.getChildren())
or get its children in sorted order (alphabetic, except for the required children in the standard-specified order):
1>>> list(item.getSortedChildren())
When there are no children, an empty list is returned.
A component’s children can be accessed through these generators (as above), or using a function:
1>>> print(item.getChildValue("version")
23.0
3>>>
Or using the contents dictionary:
1>>> print(item.contents["version"][0].value)
23.0
3>>>
Note two things when accessing the item’s properties via the contents
dictionary: first, the dictionary is a collection is lists, so even
singleton property values need to access the first list element, and
second, the object in the list is a class that holds the actual value,
accessed using the value attribute (there are other attributes of
the value available from that class instance as well).
Or using their names to access them directly as attributes:
1>>> print(item.version.value)
23.0
3>>>
When accessed as named attributes of the parsed item, singleton properties aren’t a list: you can access their value directly. If the child has parameters, in addition to its value, they are available as a dictionary:
1>>> print(item.contents["adr"][0].params)
2{'TYPE': ['WORK', 'pref']}
3>>>
Some values are structured types: names (NAME) and addresses (ADR) in vCards, for example:
1>>> address = item.contents["adr"][0].value
2>>> print(address.street)
342 Main Street
4>>> print(address.country)
5USA
6>>>
vObjects can be created by parsing (as above), or by using a helper function:
1>>> my_card = vobject.vCard()
or using the registry of known component types:
1>>> my_todo = vobject.newFromBehavior("vtodo")
Having created, and then populated a vobject as required, you can generate its serialized string format:
1>>> entry = vobject.newFromBehavior("vjournal")
2>>> entry.add("summary").value = "Summary"
3>>> entry.add("description").value = "The whole description"
4>>>
5>>> entry.serialize()
6'BEGIN:VJOURNAL\r\nDESCRIPTION:The whole description\r\n'
7'DTSTAMP:20240331T013220Z\r\nSUMMARY:Summary\r\n'
8'UID:20240331T015748Z - 66283@laptop.local\r\nEND:VJOURNAL\r\n'
9>>>
(the serialized value has been split over several lines for clarity: it is a single string, shown on a single line, in the original interpreter output).
Note that vobject has added the mandatory UID and DTSTAMP
components during serialization.
Installing¶
vobject is distributed via PyPI or from GitHub. For most people,
using pip and PyPI is easiest and best way to install, but other
options and reasons to use them are discussed in this chapter.
PyPI¶
You can install vobject using pip:
$ pip install vobject
It’s usually a good idea to install vobject into a virtual
environment, to avoid issues with incompatible versions and system-wide
packaging schemes.
Installing using pip this way will also install vobject’s
runtime dependencies, so it should be immediately ready for use.
Other Options¶
vobject is distributed as a universal wheel, and should install
from PyPI using pip without difficulty in most cases. There is
also an sdist available from PyPI and GitHub which can be used as a
fallback.
If your development environment cannot access PyPI for some reason, then downloading the wheel (or sdist) from a machine with Internet access, and transferring to your development environment would make sense.
Alternatively, you can clone the source repository from GitHub using
git. If you’re intending to modify vobject and potentially
contribute those changes back to the project, you should use the
git clone method.
Finally, the latest package source code for any release can be
downloaded from GitHub as either a tar or zip file. This
is probably not useful in the majority of cases, but it might be
useful to archive the source code for auditing or similar purposes.
Installing a wheel¶
If you’ve downloaded a wheel file (it should have a .whl suffix),
you can install it using pip as well:
$ pip install <wheel-file-name>.whl
Using this method, pip will automatically try to satisfy the runtime
dependencies by downloading their wheels in turn, unless they’re already
available in the target (virtual) environment. But it will work fine
without access to PyPI if the required dependencies are already
installed.
Installing an sdist (source distribution)¶
A Python sdist (source distribution) is a tar file (with extension
.tar.gz) produced as part of making a Python package. It is very
easily confused with the source code distribution, because the names
are basically identical, and both can have the same file extension.
sdist files can be downloaded from PyPI or from GitHub, and
will have a name like vobject-0.9.7.tar.gz. At GitHub, this file
is listed with that filename under the release assets and NOT as
the link called “Source code (tar.gz)”.
You can download the sdist manually, and then install it with pip:
$ pip install <sdist-file-name>.tar.gz
Installing from cloned source¶
If you want to use a cloned source repository, likely the best way to
install vobject is to use pip’s editable install mechanism.
First, activate the virtual environment to be used for development,
and install using pip like:
$ git clone git://github.com/py-vobject/vobject.git
$ cd vobject
$ pip install -e .
This will collect, build, and install the dependencies (from PyPI) and
then install an editable version of the vobject package (that’s
the -e parameter). An editable install directly uses the files in
your checkout area, including any local modifications you’ve made at
the time you imported the package.
Importing¶
You can import the vobject module maintaining its internal structure,
or you can import some or all bindings directly.
1import vobject
2
3card = vobject.vCard()
Or
1from vobject import *
2
3card = vCard()
All the example code in this document will use the first form, which is strongly recommended.
Note that the import * form is explicitly supported, with the
exposed namespace controlled to contain only the public features
of the package.
Calendars and Cards¶
The vCard and vCalendar specifications were originally developed by the Versit Consortium (and that’s why they’re named with a leading “v”), with contributions from Apple, IBM, AT&T/Lucent, and Siemens. Their stewardship subsequently passed to the Internet Mail Consortium, before the standards were later adopted by the IETF.
Within the IETF, vCard versions 3.0 and 4.0, and a revised and expanded calendar specification renamed to iCalendar 2.0 have been published, together with numerous extensions, clarifications, and related standards.
The two main standards (vCard and iCalendar) form a way to store and communicate information about contacts (names, addresses, phone numbers, etc), and scheduling (events, todos, etc). They have been widely adopted for both storage and sharing of contact and calendar data.
vCard¶
vCard 2.1 was the final version published by Versit, and is still widely used. vCard 3.0, published as IETF RFC-2426, is also widely used, however both versions are frequently extended by vendors or have implementation quirks in part due to unclear specification. vCard 4.0 (IETF RFC-6350) attempted to resolve the issues of earlier versions, but in doing so necessarily broke some backward compatibility, and as a result, it has not been universally adopted.
iCalendar¶
vCalendar 1.0 was published by Versit, however it was not until the renamed iCalendar 2.0 (IETF RFC-2445) was published that wide-spread interoperability was possible. Together with various extensions, and in particular the CalDAV specification for sharing calendars, iCalendar is now almost universally used for communication of calendaring data.
vObject¶
The Python vobject module supports parsing vCard and iCalendar objects
from strings (email attachments, files, etc) and generating those
formatted strings from Python card and calendar objects. It maintains
compatibility with older versions of the specifications, and supports
various quirks of widely-used implementations, simplifying real-life
usage.
vobject was originally developed by Jeffrey Harris, working at the Open
Source Applications Foundation (OSAF) on their Chandler project. It was
subsequently adopted by Sameen Karim at Eventable, before passing to
community maintenance. The source code is freely available under the
Apache 2.0 license, and developed in a public repository at GitHub.
Model¶
iCalendar and vCard are both Multipurpose Internet Mail Extension (MIME) profiles. Originally designed as a way to attach files to emails, the MIME standards include profiles for various types of things that are attached to emails from files, including calendar events and contacts, allowing email clients to understand what they are, and how to decode them.
Over time, the use of MIME types and their encoding/decoding standards has extended beyond email, particularly to web browsers, and to operating systems in general, where the concept of a “default application” for a MIME type is used to open downloaded files.
iCalendar and vCard share a MIME syntax and basic encoding mechanism that is worth explaining up front because it’s a little different to more recent alternatives such as JSON or XML.
The major elements of the model are:
Enclosure
Components
Content Lines
Parameters
Values
Each of these is discussed in detail below. It’s not necessary to
understand the full detail of these elements, but you will need at
least an overview to work with the vobject API.
Enclosure¶
A MIME enclosure (for example, an .ics file) is an ordered
sequence of octets, formatted in accordance with a standard MIME
profile. For the iCalendar and vCard profiles, the MIME
enclosure’s contents can be identified in one of two ways: using
PROFILE or using BEGIN and END.
When using PROFILE, only one object can be encoded within the MIME
enclosure. The octet stream contains an initial text line beginning
with “PROFILE” that defines the type of the object described by
the following lines. All lines in the MIME enclosure refer to a single
object of the profile type.
More commonly, using BEGIN and END allows multiple objects to
be encoded into a single MIME enclosure. This is usually used for
both iCalendar and vCard, but the PROFILE format is also
supported by vobject.
Component¶
Within the enclosure then, one or more objects are encoded. Each object is called a component which represents a complete entity: a person, an event, a journal entry, a timezone, etc. Components are described by a set of properties possibly including other nested components.
The type of the component is identified by either the PROFILE or
the BEGIN / END lines, for example like:
BEGIN:VCARD
...
END:VCARD
The component types used by the iCalendar and vCard standards include:
VCALENDARVEVENTVTODOVJOURNALVTIMEZONEVFREEBUSYVALARMVCARD
Content Line¶
A MIME enclosure exists as a sequence of octets (bytes). These octets represent characters, using a specified character encoding – typically UTF-8 in modern usage, but possibly ASCII, or other language-specific encodings, depending on the source application.
That sequence of characters is broken into physical lines by the
character pair CRLF: a Carriage Return, followed by a Line
Feed. The strings of characters separated by CRLF pairs are the
physical lines.
According to the specification, physical lines should not exceed 80
octets, including the CRLF. Because the content itself might
exceed that length, encoding first breaks the content into shorter
lines (called folding), and decoding must reassemble the content
from those broken up physical lines (called unfolding).
The unfolded content, possibly longer than 80 octets, is called a content line. Each content line within a component describes a property of that component.
A content line has a name, usually written in ALL CAPS style. It may also have zero or more parameters, and finally a value.
Parameters¶
The optional parameters of a content line either describe its encoding, or clarify its meaning within the component. Parameters have a name, and optional set of parameter values.
Example parameters include things like the BASE64 encoding of a contact’s photgraph, or the type of a phone number: voice, fax, work, home, etc.
Some properties are represented just by their name, like JPEG,
while others have one or more parameter values, like TZID=EST.
Value¶
Finally, the content line will have a value. The formatting of the value depends upon what property type is represents, and it might be either a single simple type, a sequence, or a complex multi-part object.
For exmaple, the VERSION property has a single, string-type value.
VERSION:3.0
But a vCard name property has a complex type value, with five different attributes, separated by semi-colons:
N:Public;John;Quinlan;Mr;Esq
The types of values for each standard property are defined by the
standard documents, and implemented by vobject.
Parsing¶
To parse one top level component from an existing iCalendar or
vCard stream or string, use the readOne() function:
1>>> parsedCal = vobject.readOne(icalstream)
2>>> parsedCal.vevent.dtstart.value
3datetime.datetime(2006, 2, 16, 0, 0, tzinfo=tzutc())
Similarly, readComponents() is a generator yielding one top level
component at a time from a stream or string.
1>>> vobject.readComponents(icalstream).next().vevent.dtstart.value
2datetime.datetime(2006, 2, 16, 0, 0, tzinfo=tzutc())
Parsing vCards is very similar.
1>>> s = """
2... BEGIN:VCARD
3... VERSION:3.0
4... EMAIL;TYPE=INTERNET:jeffrey@osafoundation.org
5... EMAIL;TYPE=INTERNET:jeffery@example.org
6... ORG:Open Source Applications Foundation
7... FN:Jeffrey Harris
8... N:Harris;Jeffrey;;;
9... END:VCARD
10... """
11>>> v = vobject.readOne( s )
12>>> v.prettyPrint()
13 VCARD
14 ORG: Open Source Applications Foundation
15 VERSION: 3.0
16 EMAIL: jeffrey@osafoundation.org
17 params for EMAIL:
18 TYPE [u'INTERNET']
19 FN: Jeffrey Harris
20 N: Jeffrey Harris
21>>> v.n.value.family
22u'Harris'
23>>> v.email_list
24[<EMAIL{'TYPE': ['INTERNET']}jeffrey@osafoundation.org>,
25 <EMAIL{'TYPE': ['INTERNET']}jeffery@example.org>]
Just like with the iCalendar example above, readComponents() will
yield a generator from a stream or string containing multiple vCard
objects.
1>>> vobject.readComponents(vCardStream).next().email.value
2'jeffrey@osafoundation.org'
Creating Objects¶
iCalendar¶
vObject has a basic datastructure for working with iCalendar-like syntax. Additionally, it defines specialized behaviors for many of the standard iCalendar components.
The iCalendar standard defines six object types:
VEVENT
VTODO
VJOURNAL
VTIMEZONE
VFREEBUSY
VALARM
plus the containing VCALENDAR object (note the name, a hold-over from the v1.0 Versit standard).
An iCalendar stream (eg. an .ics file) is comprised of a sequence of these objects.
Within vobject, each standard object has a defined behavior class, that specifies its allowed cardinality, base data type, ability to convert to/from native Python data types, etc. These behaviors are maintained in a registry within the vobject module, and identified by name.
To create an object that already has a behavior defined, run:
1>>> import vobject
2>>> cal = vobject.newFromBehavior('vcalendar')
3>>> cal.behavior
4<class 'vobject.icalendar.VCalendar2_0'>
Convenience functions exist to create iCalendar and vCard objects:
1>>> cal = vobject.iCalendar()
2>>> cal.behavior
3<class 'vobject.icalendar.VCalendar2_0'>
4>>> card = vobject.vCard()
5>>> card.behavior
6<class 'vobject.vcard.VCard3_0'>
Once you have an object, you can use the add method to create children:
1>>> cal.add('vevent')
2<VEVENT| []>
3>>> cal.vevent.add('summary').value = "This is a note"
4>>> cal.prettyPrint()
5 VCALENDAR
6 VEVENT
7 SUMMARY: This is a note
Note that summary is a little different from vevent, it’s a ContentLine, not a Component. It can’t have children, and it has a special value attribute.
ContentLines can also have parameters. They can be accessed with regular attribute names with _param appended:
1>>> cal.vevent.summary.x_random_param = 'Random parameter'
2>>> cal.prettyPrint()
3 VCALENDAR
4 VEVENT
5 SUMMARY: This is a note
6 params for SUMMARY:
7 X-RANDOM ['Random parameter']
There are a few things to note about this example
The underscore in x_random is converted to a dash (dashes are legal in iCalendar, underscores legal in Python)
X-RANDOM’s value is a list.
If you want to access the full list of parameters, not just the first, use <paramname>_paramlist:
1>>> cal.vevent.summary.x_random_paramlist
2['Random parameter']
3>>> cal.vevent.summary.x_random_paramlist.append('Other param')
4>>> cal.vevent.summary
5<SUMMARY{'X-RANDOM': ['Random parameter', 'Other param']}This is a note>
Similar to parameters, If you want to access more than just the first child of a Component, you can access the full list of children of a given name by appending _list to the attribute name:
1>>> cal.add('vevent').add('summary').value = "Second VEVENT"
2>>> for ev in cal.vevent_list:
3... print ev.summary.value
4This is a note
5Second VEVENT
The interaction between the del operator and the hiding of the underlying list is a little tricky, del cal.vevent and del cal.vevent_list both delete all vevent children:
1>>> first_ev = cal.vevent
2>>> del cal.vevent
3>>> cal
4<VCALENDAR| []>
5>>> cal.vevent = first_ev
VObject understands Python’s datetime module and tzinfo classes.
1>>> import datetime
2>>> utc = vobject.icalendar.utc
3>>> start = cal.vevent.add('dtstart')
4>>> start.value = datetime.datetime(2006, 2, 16, tzinfo = utc)
5>>> first_ev.prettyPrint()
6 VEVENT
7 DTSTART: 2006-02-16 00:00:00+00:00
8 SUMMARY: This is a note
9 params for SUMMARY:
10 X-RANDOM ['Random parameter', 'Other param']
Components and ContentLines have serialize methods:
1>>> cal.vevent.add('uid').value = 'Sample UID'
2>>> icalstream = cal.serialize()
3>>> print icalstream
4BEGIN:VCALENDAR
5VERSION:2.0
6PRODID:-//PYVOBJECT//NONSGML Version 1//EN
7BEGIN:VEVENT
8UID:Sample UID
9DTSTART:20060216T000000Z
10SUMMARY;X-RANDOM=Random parameter,Other param:This is a note
11END:VEVENT
12END:VCALENDAR
Observe that serializing adds missing required lines like version and prodid. A random UID would be generated, too, if one didn’t exist.
If dtstart’s tzinfo had been something other than UTC, an appropriate vtimezone would be created for it.
vCard¶
Making vCards proceeds in much the same way. Note that the ‘N’ and ‘FN’ attributes are required.
1>>> j = vobject.vCard()
2>>> j.add('n')
3 <N{} >
4>>> j.n.value = vobject.vcard.Name( family='Harris', given='Jeffrey' )
5>>> j.add('fn')
6 <FN{}>
7>>> j.fn.value ='Jeffrey Harris'
8>>> j.add('email')
9 <EMAIL{}>
10>>> j.email.value = 'jeffrey@osafoundation.org'
11>>> j.email.type_param = 'INTERNET'
12>>> j.add('org')
13 <ORG{}>
14>>> j.org.value = ['Open Source Applications Foundation']
15>>> j.prettyPrint()
16 VCARD
17 ORG: ['Open Source Applications Foundation']
18 EMAIL: jeffrey@osafoundation.org
19 params for EMAIL:
20 TYPE ['INTERNET']
21 FN: Jeffrey Harris
22 N: Jeffrey Harris
serializing will add any required computable attributes (like ‘VERSION’)
1>>> j.serialize()
2'BEGIN:VCARD\r\nVERSION:3.0\r\nEMAIL;TYPE=INTERNET:jeffrey@osafoundation.org\r\nFN:Jeffrey Harris\r\nN:Harris;Jeffrey;;;\r\nORG:Open Source Applications Foundation\r\nEND:VCARD\r\n'
3>>> j.prettyPrint()
4 VCARD
5 ORG: Open Source Applications Foundation
6 VERSION: 3.0
7 EMAIL: jeffrey@osafoundation.org
8 params for EMAIL:
9 TYPE ['INTERNET']
10 FN: Jeffrey Harris
11 N: Jeffrey Harris
Common Problems¶
Non-ASCII characters
Selecting a serialization format version
Validation
Flags controlling compatibility with popular application’s bugs
Getting Help¶
TBD