2. Conventions

2.1. Objects

There is a one-to-one relationship between objects in MAPI and the objects presented to Python. MAPI provides two main entrypoints in a classfactory-pattern to obtain references to the top-level objects.

These functions are:

MAPIAdminProfiles()
MAPILogonEx()

These functions can be used to create and manage MAPI profiles, and log on to one of those profiles, respectively. Each call returns a top-level object which can be used to subsequently open other objects:

import MAPI

profadmin = MAPI.MAPIAdminProfiles(0)
profadmin.CreateProfile('profile','password', 0, 0)

...

session = MAPI.MAPILogonEx(0, 'profile','password', 0)
session.GetMsgStoresTable(0)

2.2. Modules

The default MAPI package imports the C++ bindings, functions and methods. However, there are also some other modules that make life easier for the Python MAPI programmer:

2.2.1. MAPI.Struct

Python representations of MAPI structs. These contain data that cannot be described as a simple int or string. Naming of classes in this module mirrors the MAPI Struct as in C++ and provides convenient constructors to create data structures from Python

import MAPI.Struct
import MAPI.Tags

prop = SPropValue(MAPI.Tags.PR_SUBJECT, 'hello, world!')

2.2.2. MAPI.Time

Since MAPI uses a timestamp format that is not very common in the Python world called FILETIME (100-ns periods since 1 jan 1601), MAPI.Time offers an easy way to convert to and from the standard epoch-based timestamps normally used in Unix:

import MAPI.Time

t = FileTime(10000000000000)
print t.unixtime

t = unixtime(1234567890)
print t.filetime

All PT_SYSTIME values in MAPI will be converted to/from this format.

Note

FILETIME has a much wider time frame and greater precision than unix timestamps. Allthough in practice a precision of 1 second is usually fine, it may cause subtle problems if you are assuming the full FILETIME precision.

2.2.3. MAPI.Tags

The MAPI.Tags module contains constants for all well-known MAPI property tags:

import MAPI.Tags

print MAPI.Tags.PR_SUBJECT
print MAPI.Tags.PR_SUBJECT_A
print MAPI.Tags.PR_SUBJECT_W

The constants are identical to those used in C++ MAPI. By default, string tags use the PT_STRING8 (terminal charset) type. If you wish to use the PT_UNICODE variant, either use the _W type (eg PR_SUBJECT_W) or use the following construct:

import MAPI
MAPI.unicode = True
import MAPI.Tags

print PROP_TYPE(MAPI.Tags.PR_SUBJECT)
# outputs 31 (PT_UNICODE)

This will cause all properties to default to the PT_UNICODE property type.

2.2.4. MAPI.Defs

Provides convenience functions (also identical to their C++ counterparts) to create, test and modify property tags, and other utility functions:

import MAPI.Defs

PR_SUBJECT = MAPI.Defs.PROP_TAG(PT_STRING8, 0x0037)
type = MAPI.Defs.PROP_TYPE(PR_SUBJECT)
id = MAPI.Defs.PROP_ID(PR_SUBJECT)

2.2.5. MAPI.Util

MAPI.Util contains some useful functions for profile creation.

2.2.5.1. OpenECSession()

Opens a session for the given user/password pair on the specified path for a Kopano server.

import MAPI
from MAPI.Util import *

session = OpenECSession('joe','password','file:///var/run/kopano/server.sock')

2.2.5.2. GetDefaultStore()

Opens the default store in a session.

import MAPI
from MAPI.Util import *

session = OpenECSession('joe','password','file:///var/run/kopano/server.sock')
store = GetDefaultStore(session)

2.3. Parameters

Parameters passed in python are generally equal to their C++ counterpart. For example, the function

MAPILogonEx(0, "profile", "password", 0, &lpStore)

is identical to the python call

store = MAPILogonEx(0, "profile", "password", 0)

There are some small differences:

  • All count/arraypointer pairs are represented by a single list in python, e.g.:
object->SetProps(2, lpSomePointer, NULL)

in python:

object.SetProps([prop1, prop2], None)
  • NULL values in C++ are represented by None values in python
  • All ULONG/LPENTRYID pairs are represented by a single string (containing the raw binary entryid)
  • Return values in C++ are returned (in the order of the original C++ parameters) in the return value of the python call. If there is more than 1 return value, a list is returned.

2.4. Errors

In the C++ interface, each method call returns a HRESULT containing a success or failure value. Since python is an exception-based language, all HRESULT errors are stripped from the call and ignored if there is no error. If an error has occurred, an exception of the MAPI.Exception type is raised.

Although this creates a pleasant look of the code, it does have one major drawback. In MAPI, there are fatal errors (which have the top bit set, so have values 0x8xxxxxxx) and warning errors (which do not have the top bit set). However, warnings actually behave more like success cases; the function still returns a value, but some minor part of the operation apparently failed.

Since there is no such thing as a warning in exceptions, currently warnings are treated exactly the same as success. Possibly future versions will change this, but will definitely not raise an exception.

2.5. Flags

Many MAPI calls have an ulFlags parameter that can be passed to control various things inside the call. The python interface supports all the flags that would normally be passed to the MAPI C++ interface, since it is just and int that is blindly transferred to MAPI. However there is one flag, MAPI_UNICODE, that has a direct affect on how the passed parameters are passed to MAPI. For more information see the Character sets and Unicode part in this document.

2.6. Methods

Method names in python are completely analogous to their C++ counterparts, including case.

2.7. Character sets and Unicode

Character sets are a little bit nasty. The reason for this is that we are working with three (variable) charsets in the Python bindings:

  • Your terminal charset (depends on your locale, most modern OS’s default to utf-8 but many still use iso-8859-1 or other local charsets)
  • The sys.getdefaultencoding() charset (depends on your site.py settings but defaults to ascii)
  • The internal MAPI charset (windows-1252)

What’s more, a user can send information using a string or a unicode type in python.

This is the way that charsets are used:

  • Since sys.getdefaultencoding() isn’t easy to change for each application, it is not used. This in turn means that we never do any conversions between string and unicode in the python binding since this would require using sys.getdefaultencoding(). This would cause a lot of confusion since passing a unicode string without the MAPI_UNICODE flag would cause the unicode to be converted back to string (using ascii) and probably make python complain about the non asciiness of your unicode string, which is confusing to say the least, since the python binding itself would then have to convert from the string charset back into whatever charset MAPI was expecting.
  • String input data is assumed to be in Your terminal charset
  • Strings output by MAPI are in Your terminal charset
  • When passing the MAPI_UNICODE flag in flags or when using the PT_UNICODE property type you must pass a unicode string (u’string’). Failure to do so will result in a raised exception.

The nice thing about this is that when you parse commandline arguments or when you are printing to the terminal, you never have to do any charset conversions. The drawback is that if you know that you are receiving, say, UTF-8 from some other library (eg. an XML reader), then you can do any of two things:

  1. Make sure that the current locale is in utf-8 (use the locale command from the bash shell to check your locale)
  2. Convert the utf-8 data from the other library to unicode strings and use the PT_UNICODE data types (and possibly MAPI_UNICODE flag, but this only affects strings in the argument list of a method call):
message = folder.CreateMessage(0)
s = 'some string from XML lib'

message.SetProps([SPropValue(PR_SUBJECT_W, s.decode('utf-8'))]);

2.8. Memory management

Obviously you don’t have to worry about memory management yourself. The python bindings will correctly release MAPI objects when their reference count in python reaches 0. Internal referencing inside MAPI allows for reverse-order releases:

folder = store.OpenEntry(None, None, 0)
message = folder.CreateMessage(0)

folder = None
message = None

Although the folder is released first, the fact that another message object was opened on it before prevents the actual MAPI object from being freed. Once all its children have been freed, the top-level object will free any resources used.