def __init__(self, *args, **kwargs): version = kwargs.pop('version', None) super(Protocol, self).__init__(*args, **kwargs) scheme = 'https' if self.has_ssl else 'http' self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server) self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server) self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server) # Autodetect authentication type if necessary # pylint: disable=access-member-before-definition if self.auth_type is None: self.auth_type = get_service_authtype( service_endpoint=self.service_endpoint, versions=API_VERSIONS, name=self.credentials.username) # Default to the auth type used by the service. We only need this if 'version' is None self.docs_auth_type = self.auth_type # Try to behave nicely with the Exchange server. We want to keep the connection open between requests. # We also want to re-use sessions, to avoid the NTLM auth handshake on every request. pool_size = self.pool_size or self.SESSION_POOLSIZE self._session_pool = LifoQueue(maxsize=pool_size) for _ in range(pool_size): self._session_pool.put(self.create_session(), block=False) if version: isinstance(version, Version) self.version = version else: # Version.guess() needs auth objects and a working session pool try: # Try to get the auth_type of 'types.xsd' so we can fetch it and look at the version contained there self.docs_auth_type = get_docs_authtype( docs_url=self.types_url) except TransportError: pass self.version = Version.guess(self) # Used by services to process service requests that are able to run in parallel. Thread pool should be # larger than the connection pool so we have time to process data without idling the connection. # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would # leave open threads around to be garbage collected. if self.pool_size: thread_poolsize = 4 * self.pool_size else: thread_poolsize = 4 * self.SESSION_POOLSIZE self.thread_pool = ThreadPool(processes=thread_poolsize)
class AutodiscoverProtocol(BaseProtocol): # Protocol which implements the bare essentials for autodiscover TIMEOUT = 10 # Seconds def __init__(self, *args, **kwargs): super(AutodiscoverProtocol, self).__init__(*args, **kwargs) self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE) for _ in range(self.SESSION_POOLSIZE): self._session_pool.put(self.create_session(), block=False) def __str__(self): return '''\ Autodiscover endpoint: %s Auth type: %s''' % ( self.service_endpoint, self.auth_type, )
class Protocol(with_metaclass(CachingProtocol, BaseProtocol)): def __init__(self, *args, **kwargs): version = kwargs.pop('version', None) super(Protocol, self).__init__(*args, **kwargs) scheme = 'https' if self.has_ssl else 'http' self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server) self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server) self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server) # Autodetect authentication type if necessary # pylint: disable=access-member-before-definition if self.auth_type is None: self.auth_type = get_service_authtype( service_endpoint=self.service_endpoint, versions=API_VERSIONS, name=self.credentials.username) # Try to behave nicely with the Exchange server. We want to keep the connection open between requests. # We also want to re-use sessions, to avoid the NTLM auth handshake on every request. pool_size = self.pool_size or self.SESSION_POOLSIZE self._session_pool = LifoQueue(maxsize=pool_size) for _ in range(pool_size): self._session_pool.put(self.create_session(), block=False) if version: isinstance(version, Version) self.version = version else: # Version.guess() needs auth objects and a working session pool self.version = Version.guess(self) # Used by services to process service requests that are able to run in parallel. Thread pool should be # larger than the connection pool so we have time to process data without idling the connection. # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would # leave open threads around to be garbage collected. if self.pool_size: thread_poolsize = 4 * self.pool_size else: thread_poolsize = 4 * self.SESSION_POOLSIZE self.thread_pool = ThreadPool(processes=thread_poolsize) def get_timezones(self, timezones=None, return_full_timezone_data=False): """ Get timezone definitions from the server :param timezones: A list of EWSDateTime instances. If None, fetches all timezones from server :param return_full_timezone_data: If true, also returns periods and transitions :return: A list of (tz_id, name, periods, transitions) tuples """ return GetServerTimeZones(protocol=self).call( timezones=timezones, return_full_timezone_data=return_full_timezone_data) def get_free_busy_info(self, accounts, start, end, merged_free_busy_interval=30, requested_view='DetailedMerged'): """ Returns free/busy information for a list of accounts :param accounts: A list of (account, attendee_type, exclude_conflicts) tuples, where account is an Account object, attendee_type is a MailboxData.attendee_type choice, and exclude_conflicts is a boolean. :param start: The start datetime of the request :param end: The end datetime of the request :param merged_free_busy_interval: The interval, in minutes, of merged free/busy information :param requested_view: The type of information returned. Possible values are defined in the FreeBusyViewOptions.requested_view choices. :return: A generator of FreeBusyView objects """ from .account import Account for account, attendee_type, exclude_conflicts in accounts: if not isinstance(account, Account): raise ValueError( "'accounts' item %r must be an 'Account' instance" % account) if attendee_type not in MailboxData.ATTENDEE_TYPES: raise ValueError("'accounts' item %r must be one of %s" % (attendee_type, MailboxData.ATTENDEE_TYPES)) if not isinstance(exclude_conflicts, bool): raise ValueError( "'accounts' item %r must be a 'bool' instance" % exclude_conflicts) if start >= end: raise ValueError("'start' must be less than 'end' (%s -> %s)" % (start, end)) if not isinstance(merged_free_busy_interval, int): raise ValueError( "'merged_free_busy_interval' value %r must be an 'int'" % merged_free_busy_interval) if requested_view not in FreeBusyViewOptions.REQUESTED_VIEWS: raise ValueError( "'requested_view' value %r must be one of %s" % (requested_view, FreeBusyViewOptions.REQUESTED_VIEWS)) _, _, periods, transitions, transitions_groups = list( self.get_timezones(timezones=[start.tzinfo], return_full_timezone_data=True))[0] return GetUserAvailability(self).call( timezone=TimeZone.from_server_timezone( periods=periods, transitions=transitions, transitionsgroups=transitions_groups, for_year=start.year), mailbox_data=[ MailboxData(email=account.primary_smtp_address, attendee_type=attendee_type, exclude_conflicts=exclude_conflicts) for account, attendee_type, exclude_conflicts in accounts ], free_busy_view_options=FreeBusyViewOptions( time_window=TimeWindow(start=start, end=end), merged_free_busy_interval=merged_free_busy_interval, requested_view=requested_view, ), ) def get_roomlists(self): return GetRoomLists(protocol=self).call() def get_rooms(self, roomlist): from .properties import RoomList return GetRooms(protocol=self).call(roomlist=RoomList( email_address=roomlist)) def resolve_names(self, names, return_full_contact_data=False, search_scope=None, shape=None): """ Resolve accounts on the server using partial account data, e.g. an email address or initials :param names: A list of identifiers to query :param return_full_contact_data: If True, returns full contact data :param search_scope: The scope to perform the search. Must be one of SEARCH_SCOPE_CHOICES :param shape: :return: A list of Mailbox items or, if return_full_contact_data is True, tuples of (Mailbox, Contact) items """ from .items import SHAPE_CHOICES, SEARCH_SCOPE_CHOICES if search_scope: if search_scope not in SEARCH_SCOPE_CHOICES: raise ValueError("'search_scope' %s must be one if %s" % (search_scope, SEARCH_SCOPE_CHOICES)) if shape: if shape not in SHAPE_CHOICES: raise ValueError("'shape' %s must be one if %s" % (shape, SHAPE_CHOICES)) return list( ResolveNames(protocol=self).call( unresolved_entries=names, return_full_contact_data=return_full_contact_data, search_scope=search_scope, contact_data_shape=shape, )) def get_searchable_mailboxes(self, search_filter=None, expand_group_membership=False): """This method is only available to users who have been assigned the Discovery Management RBAC role. See https://technet.microsoft.com/en-us/library/jj200692(v=exchg.150).aspx. :param search_filter: Is set, must be a single email alias :param expand_group_membership: If True, returned distribution lists are expanded :return: a list of SearchableMailbox, FailedMailbox or Exception instances """ return list( GetSearchableMailboxes(protocol=self).call( search_filter=search_filter, expand_group_membership=expand_group_membership, )) def __str__(self): return '''\ EWS url: %s Product name: %s EWS API version: %s Build number: %s EWS auth: %s''' % ( self.service_endpoint, self.version.fullname, self.version.api_version, self.version.build, self.auth_type, )
def _create_session_pool(self): # Create a pool to reuse sessions containing connections to the server session_pool = LifoQueue(maxsize=self._session_pool_size) for _ in range(self._session_pool_size): session_pool.put(self.create_session(), block=False) return session_pool
class Protocol(with_metaclass(CachingProtocol, BaseProtocol)): def __init__(self, *args, **kwargs): version = kwargs.pop('version', None) super(Protocol, self).__init__(*args, **kwargs) scheme = 'https' if self.has_ssl else 'http' self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server) self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server) self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server) # Autodetect authentication type if necessary if self.auth_type is None: self.auth_type = get_service_authtype( service_endpoint=self.service_endpoint, versions=API_VERSIONS, name=self.credentials.username) # Default to the auth type used by the service. We only need this if 'version' is None self.docs_auth_type = self.auth_type # Try to behave nicely with the Exchange server. We want to keep the connection open between requests. # We also want to re-use sessions, to avoid the NTLM auth handshake on every request. self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE) for _ in range(self.SESSION_POOLSIZE): self._session_pool.put(self.create_session(), block=False) if version: isinstance(version, Version) self.version = version else: # Version.guess() needs auth objects and a working session pool try: # Try to get the auth_type of 'types.xsd' so we can fetch it and look at the version contained there self.docs_auth_type = get_docs_authtype( docs_url=self.types_url) except TransportError: pass self.version = Version.guess(self) # Used by services to process service requests that are able to run in parallel. Thread pool should be # larger than the connection pool so we have time to process data without idling the connection. # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would # leave open threads around to be garbage collected. thread_poolsize = 4 * self.SESSION_POOLSIZE self.thread_pool = ThreadPool(processes=thread_poolsize) def get_timezones(self, timezones=None, return_full_timezone_data=False): """ Get timezone definitions from the server :param timezones: A list of EWSDateTime instances. If None, fetches all timezones from server :param return_full_timezone_data: If true, also returns periods and transitions :return: A list of (tz_id, name, periods, transitions) tuples """ return GetServerTimeZones(protocol=self).call( timezones=timezones, return_full_timezone_data=return_full_timezone_data) def get_free_busy_info(self, accounts, start, end, merged_free_busy_interval=30, requested_view='DetailedMerged'): """ Returns free/busy information for a list of accounts :param accounts: A list of (account, attendee_type, exclude_conflicts) tuples, where account is an Account object, attendee_type is a MailboxData.attendee_type choice, and exclude_conflicts is a boolean. :param start: The start datetime of the request :param end: The end datetime of the request :param merged_free_busy_interval: The interval, in minutes, of merged free/busy information :param requested_view: The type of information returned. Possible values are defined in the FreeBusyViewOptions.requested_view choices. :return: A generator of FreeBusyView objects """ from .account import Account attendee_type_choices = { c.value for c in MailboxData.get_field_by_fieldname( 'attendee_type').choices } for account, attendee_type, exclude_conflicts in accounts: if not isinstance(account, Account): raise ValueError( "'accounts' item %r must be an 'Account' instance" % account) if attendee_type not in attendee_type_choices: raise ValueError("'accounts' item %r must be one of %s" % (attendee_type, attendee_type_choices)) if not isinstance(exclude_conflicts, bool): raise ValueError( "'accounts' item %r must be a 'bool' instance" % exclude_conflicts) if start >= end: raise ValueError("'start' must be less than 'end' (%s -> %s)" % (start, end)) if not isinstance(merged_free_busy_interval, int): raise ValueError( "'merged_free_busy_interval' value %r must be an 'int'" % merged_free_busy_interval) requested_view_choices = { c.value for c in FreeBusyViewOptions.get_field_by_fieldname( 'requested_view').choices } if requested_view not in requested_view_choices: raise ValueError("'requested_view' value %r must be one of %s" % (requested_view, requested_view_choices)) tz = start.tzinfo # The timezone of the start and end dates for_year = start.year _, _, periods, transitions, transitions_groups = list( self.get_timezones(timezones=[tz], return_full_timezone_data=True))[0] timezone = TimeZone.from_server_timezone(periods, transitions, transitions_groups, for_year=for_year) mailbox_data = list( MailboxData(email=account.primary_smtp_address, attendee_type=attendee_type, exclude_conflicts=exclude_conflicts) for account, attendee_type, exclude_conflicts in accounts) return GetUserAvailability(self).call( timezone=timezone, mailbox_data=mailbox_data, free_busy_view_options=FreeBusyViewOptions( time_window=TimeWindow(start=start, end=end), merged_free_busy_interval=merged_free_busy_interval, requested_view=requested_view, ), ) def get_roomlists(self): return GetRoomLists(protocol=self).call() def get_rooms(self, roomlist): from .properties import RoomList return GetRooms(protocol=self).call(roomlist=RoomList( email_address=roomlist)) def resolve_names(self, names, return_full_contact_data=False, search_scope=None, shape=None): from .items import SHAPE_CHOICES, SEARCH_SCOPE_CHOICES if search_scope: if search_scope not in SEARCH_SCOPE_CHOICES: raise ValueError("'search_scope' %s must be one if %s" % (search_scope, SEARCH_SCOPE_CHOICES)) if shape: if shape not in AUTH_TYPE_MAP: raise ValueError("'shape' %s must be one if %s" % (shape, SHAPE_CHOICES)) return list( ResolveNames(protocol=self).call( unresolved_entries=names, return_full_contact_data=return_full_contact_data, search_scope=search_scope, contact_data_shape=shape, )) def get_searchable_mailboxes(self, search_filter=None, expand_group_membership=False): return GetSearchableMailboxes(protocol=self).call( search_filter=search_filter, expand_group_membership=expand_group_membership, ) def __str__(self): return '''\ EWS url: %s Product name: %s EWS API version: %s Build number: %s EWS auth: %s XSD auth: %s''' % ( self.service_endpoint, self.version.fullname, self.version.api_version, self.version.build, self.auth_type, self.docs_auth_type, )
def __init__(self, *args, **kwargs): super(AutodiscoverProtocol, self).__init__(*args, **kwargs) self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE) for _ in range(self.SESSION_POOLSIZE): self._session_pool.put(self.create_session(), block=False)
class Protocol(with_metaclass(CachingProtocol, BaseProtocol)): def __init__(self, *args, **kwargs): version = kwargs.pop('version', None) super(Protocol, self).__init__(*args, **kwargs) scheme = 'https' if self.has_ssl else 'http' self.wsdl_url = '%s://%s/EWS/Services.wsdl' % (scheme, self.server) self.messages_url = '%s://%s/EWS/messages.xsd' % (scheme, self.server) self.types_url = '%s://%s/EWS/types.xsd' % (scheme, self.server) # Autodetect authentication type if necessary if self.auth_type is None: self.auth_type = get_service_authtype( service_endpoint=self.service_endpoint, versions=API_VERSIONS, name=self.credentials.username) # Default to the auth type used by the service. We only need this if 'version' is None self.docs_auth_type = self.auth_type # Try to behave nicely with the Exchange server. We want to keep the connection open between requests. # We also want to re-use sessions, to avoid the NTLM auth handshake on every request. self._session_pool = LifoQueue(maxsize=self.SESSION_POOLSIZE) for _ in range(self.SESSION_POOLSIZE): self._session_pool.put(self.create_session(), block=False) if version: isinstance(version, Version) self.version = version else: # Version.guess() needs auth objects and a working session pool try: # Try to get the auth_type of 'types.xsd' so we can fetch it and look at the version contained there self.docs_auth_type = get_docs_authtype( docs_url=self.types_url) except TransportError: pass self.version = Version.guess(self) # Used by services to process service requests that are able to run in parallel. Thread pool should be # larger than the connection pool so we have time to process data without idling the connection. # Create the pool as the last thing here, since we may fail in the version or auth type guessing, which would # leave open threads around to be garbage collected. thread_poolsize = 4 * self.SESSION_POOLSIZE self.thread_pool = ThreadPool(processes=thread_poolsize) def get_timezones(self): return GetServerTimeZones(protocol=self).call() def get_roomlists(self): return GetRoomLists(protocol=self).call() def get_rooms(self, roomlist): from .properties import RoomList return GetRooms(protocol=self).call(roomlist=RoomList( email_address=roomlist)) def resolve_names(self, names, return_full_contact_data=False, search_scope=None, shape=None): from .items import SHAPE_CHOICES, SEARCH_SCOPE_CHOICES if search_scope: assert search_scope in SEARCH_SCOPE_CHOICES if shape: assert shape in SHAPE_CHOICES return list( ResolveNames(protocol=self).call( unresolved_entries=names, return_full_contact_data=return_full_contact_data, search_scope=search_scope, contact_data_shape=shape, )) def __str__(self): return '''\ EWS url: %s Product name: %s EWS API version: %s Build number: %s EWS auth: %s XSD auth: %s''' % ( self.service_endpoint, self.version.fullname, self.version.api_version, self.version.build, self.auth_type, self.docs_auth_type, )