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:
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)
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:
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!')
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.
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.
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)
MAPI.Util contains some useful functions for profile creation.
Opens a session for the given user/password pair on the specified path for a Zarafa server.
import MAPI from MAPI.Util import * session = OpenECSession('joe','password','file:///var/run/zarafa')
Opens the default store in a session.
import MAPI from MAPI.Util import * session = OpenECSession('joe','password','file:///var/run/zarafa') store = GetDefaultStore(session)
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)
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.
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.
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.
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
mustpass 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:
- Make sure that the current locale is in utf-8 (use the locale command from the bash shell to check your locale)
- 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'))]);
Since the release of version 7.0 Zarafa has server-wide support for unicode, but every older version only support the windows-1252 (almost identical to iso-8859-15 or Latin-1) charset internally. Which means that although using unicode strings in versions prior to 7.0 is supported, any character outside the windows-1252 charset will be converted to a questionmark symbol (?).
The python interface has not changed with this internal change to unicode. Python programs written for Zarafa 6.30 or 6.40 will continue to work unchanged on 7.0 and upwards.
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.