{ "info": { "author": "Janscas, Roycem90, Narcolapser", "author_email": "janscas@users.noreply.github.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Office/Business :: Office Suites", "Topic :: Software Development :: Libraries" ], "description": "[](https://pepy.tech/project/O365)\n[](https://pypi.python.org/pypi/O365)\n[](https://pypi.python.org/pypi/O365/)\n[](https://travis-ci.org/O365/python-o365)\n\n# O365 - Microsoft Graph and Office 365 API made easy\n\n\n> Detailed usage documentation is still in progress\n\nThis project aims is to make interact with Microsoft Graph and Office 365 easy to do in a Pythonic way. \nAccess to Email, Calendar, Contacts, OneDrive, etc. Are easy to do in a way that feel easy and straight forward to beginners and feels just right to seasoned python programmer.\n\nThe project is currently developed and maintained by [Janscas](https://github.com/janscas). \n\n#### Core developers\n- [Toben Archer](https://github.com/Narcolapser)\n- [Geethanadh](https://github.com/GeethanadhP)\n- [Janscas](https://github.com/janscas)\n\n**We are always open to new pull requests!**\n\n#### Rebuilding HTML Docs\n- Install `sphinx` python library\n \n `pip install sphinx==2.2.2`\n\n- Run the shell script `build_docs.sh`, or copy the command from the file when using on windows\n\n\n#### Quick example on sending a message:\n\n```python\nfrom O365 import Account\n\ncredentials = ('client_id', 'client_secret')\n\naccount = Account(credentials)\nm = account.new_message()\nm.to.add('to_example@example.com')\nm.subject = 'Testing!'\nm.body = \"George Best quote: I've stopped drinking, but only while I'm asleep.\"\nm.send()\n```\n\n\n### Why choose O365?\n- Almost Full Support for MsGraph and Office 365 Rest Api.\n- Good Abstraction layer between each Api. Change the api (Graph vs Office365) and don't worry about the api internal implementation.\n- Full oauth support with automatic handling of refresh tokens.\n- Automatic handling between local datetimes and server datetimes. Work with your local datetime and let this library do the rest.\n- Change between different resource with ease: access shared mailboxes, other users resources, sharepoint resources, etc.\n- Pagination support through a custom iterator that handles future requests automatically. Request Infinite items!\n- A query helper to help you build custom OData queries (filter, order, select and search).\n- Modular ApiComponents can be created and built to achieve further functionality.\n\n___\n\nThis project was also a learning resource for us. This is a list of not so common python idioms used in this project:\n- New unpacking technics: `def method(argument, *, with_name=None, **other_params):`\n- Enums: `from enum import Enum`\n- Factory paradigm\n- Package organization\n- Timezone conversion and timezone aware datetimes\n- Etc. ([see the code!](https://github.com/O365/python-o365/tree/master/O365))\n\n\nWhat follows is kind of a wiki...\n\n## Table of contents\n\n- [Install](#install)\n- [Usage](#usage)\n- [Authentication](#authentication)\n- [Protocols](#protocols)\n- [Account Class and Modularity](#account)\n- [MailBox](#mailbox)\n- [AddressBook](#addressbook)\n- [Directory and Users](#directory-and-users)\n- [Calendar](#calendar)\n- [Tasks](#tasks)\n- [OneDrive](#onedrive)\n- [Excel](#excel)\n- [Sharepoint](#sharepoint)\n- [Planner](#planner)\n- [Outlook Categories](#outlook-categories)\n- [Utils](#utils)\n\n\n## Install\nO365 is available on pypi.org. Simply run `pip install O365` to install it.\n\nRequirements: >= Python 3.4\n\nProject dependencies installed by pip:\n - requests\n - requests-oauthlib\n - beatifulsoup4\n - stringcase\n - python-dateutil\n - tzlocal\n - pytz\n \n \n## Usage\nThe first step to be able to work with this library is to register an application and retrieve the auth token. See [Authentication](#authentication).\n\nIt is highly recommended to add the \"offline_access\" permission and request this scope when authenticating. Otherwise the library will only have access to the user resources for 1 hour. See [Permissions and Scopes](#permissions-and-scopes).\n\nWith the access token retrieved and stored you will be able to perform api calls to the service.\n\nA common pattern to check for authentication and use the library is this one:\n\n```python\nscopes = ['my_required_scopes'] # you can use scope helpers here (see Permissions and Scopes section)\n\naccount = Account(credentials)\n\nif not account.is_authenticated: # will check if there is a token and has not expired\n # ask for a login\n # console based authentication See Authentication for other flows\n account.authenticate(scopes=scopes)\n\n# now we are autheticated\n# use the library from now on\n\n# ...\n```\n\n## Authentication\nYou can only authenticate using oauth athentication as Microsoft deprecated basic auth on November 1st 2018.\n\nThere are currently three authentication methods:\n\n- [Authenticate on behalf of a user](https://docs.microsoft.com/en-us/graph/auth-v2-user?context=graph%2Fapi%2F1.0&view=graph-rest-1.0): \nAny user will give consent to the app to access it's resources. \nThis oauth flow is called **authorization code grant flow**. This is the default authentication method used by this library.\n- [Authenticate on behalf of a user (public)](https://docs.microsoft.com/en-us/graph/auth-v2-user?context=graph%2Fapi%2F1.0&view=graph-rest-1.0):\nSame as the former but for public apps where the client secret can't be secured. Client secret is not required.\n- [Authenticate with your own identity](https://docs.microsoft.com/en-us/graph/auth-v2-service?context=graph%2Fapi%2F1.0&view=graph-rest-1.0): \nThis will use your own identity (the app identity). This oauth flow is called **client credentials grant flow**. \n\n > 'Authenticate with your own identity' is not an allowed method for **Microsoft Personal accounts**. \n\nWhen to use one or the other and requirements:\n\n Topic | On behalf of a user *(auth_flow_type=='authorization')* | On behalf of a user (public) *(auth_flow_type=='public')* | With your own identity *(auth_flow_type=='credentials')*\n :---: | :---: | :---: | :---:\n **Register the App** | Required | Required | Required\n **Requires Admin Consent** | Only on certain advanced permissions | Only on certain advanced permissions | Yes, for everything\n **App Permission Type** | Delegated Permissions (on behalf of the user) | Delegated Permissions (on behalf of the user) | Application Permissions\n **Auth requirements** | Client Id, Client Secret, Authorization Code | Client Id, Authorization Code | Client Id, Client Secret\n **Authentication** | 2 step authentication with user consent | 2 step authentication with user consent | 1 step authentication\n **Auth Scopes** | Required | Required | None\n **Token Expiration** | 60 Minutes without refresh token or 90 days* | 60 Minutes without refresh token or 90 days* | 60 Minutes*\n **Login Expiration** | Unlimited if there is a refresh token and as long as a re| Unlimited if there is a refresh token and as long as a refresh is done within the 90 days | Unlimited\n **Resources** | Access the user resources, and any shared resources | Access the user resources, and any shared resources | All Azure AD users the app has access to\n **Microsoft Account Type** | Any | Any | Not Allowed for Personal Accounts\n **Tenant ID Required** | Defaults to \"common\" | Defaults to \"common\" | Required (can't be \"common\")\n\n**O365 will automatically refresh the token for you on either authentication method. The refresh token lasts 90 days but it's refreshed on each connection so as long as you connect within 90 days you can have unlimited access.*\n\nThe `Connection` Class handles the authentication.\n\n\n#### Oauth Authentication\nThis section is explained using Microsoft Graph Protocol, almost the same applies to the Office 365 REST API.\n\n##### Authentication Steps\n1. To allow authentication you first need to register your application at [Azure App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade).\n\n 1. Login at [Azure Portal (App Registrations)](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)\n 1. Create an app. Set a name.\n 1. In Supported account types choose \"Accounts in any organizational directory and personal Microsoft accounts (e.g. Skype, Xbox, Outlook.com)\", if you are using a personal account.\n 1. Set the redirect uri (Web) to: `https://login.microsoftonline.com/common/oauth2/nativeclient` and click register. This needs to be inserted into the \"Redirect URI\" text box as simply checking the check box next to this link seems to be insufficent. This is the default redirect uri used by this library, but you can use any other if you want. \n 1. Write down the Application (client) ID. You will need this value.\n 1. Under \"Certificates & secrets\", generate a new client secret. Set the expiration preferably to never. Write down the value of the client secret created now. It will be hidden later on.\n 1. Under Api Permissions:\n - When authenticating \"on behalf of a user\":\n 1. add the **delegated permissions** for Microsoft Graph you want (see scopes).\n 1. It is highly recommended to add \"offline_access\" permission. If not the user you will have to re-authenticate every hour.\n - When authenticating \"with your own identity\":\n 1. add the **application permissions** for Microsoft Graph you want.\n 1. Click on the Grant Admin Consent button (if you have admin permissions) or wait until the admin has given consent to your application.\n \n As an example, to read and send emails use:\n 1. Mail.ReadWrite\n 1. Mail.Send\n 1. User.Read\n \n1. Then you need to login for the first time to get the access token that will grant access to the user resources.\n \n To authenticate (login) you can use [different authentication interfaces](#different-authentication-interfaces). On the following examples we will be using the Console Based Interface but you can use any one.\n \n - When authenticating on behalf of a user:\n \n > **Important:** In case you can't secure the client secret you can use the auth flow type 'public' which only requires the client id. \n \n 1. Instantiate an `Account` object with the credentials (client id and client secret).\n 1. Call `account.authenticate` and pass the scopes you want (the ones you previously added on the app registration portal).\n \n > Note: when using the \"on behalf of a user\" authentication, you can pass the scopes to either the `Account` init or to the authenticate method. Either way is correct. \n \n You can pass \"protocol scopes\" (like: \"https://graph.microsoft.com/Calendars.ReadWrite\") to the method or use \"[scope helpers](https://github.com/O365/python-o365/blob/master/O365/connection.py#L34)\" like (\"message_all\").\n If you pass protocol scopes, then the `account` instance must be initialized with the same protocol used by the scopes. By using scope helpers you can abstract the protocol from the scopes and let this library work for you. \n Finally, you can mix and match \"protocol scopes\" with \"scope helpers\".\n Go to the [procotol section](#protocols) to know more about them.\n \n For Example (following the previous permissions added):\n \n ```python\n from O365 import Account\n credentials = ('my_client_id', 'my_client_secret')\n \n # the default protocol will be Microsoft Graph\n # the default authentication method will be \"on behalf of a user\"\n \n account = Account(credentials)\n if account.authenticate(scopes=['basic', 'message_all']):\n print('Authenticated!')\n \n # 'basic' adds: 'offline_access' and 'https://graph.microsoft.com/User.Read'\n # 'message_all' adds: 'https://graph.microsoft.com/Mail.ReadWrite' and 'https://graph.microsoft.com/Mail.Send'\n ```\n When using the \"on behalf of the user\" authentication method, this method call will print a url that the user must visit to give consent to the app on the required permissions.\n \n The user must then visit this url and give consent to the application. When consent is given, the page will rediret to: \"https://login.microsoftonline.com/common/oauth2/nativeclient\" by default (you can change this) with a url query param called 'code'.\n \n Then the user must copy the resulting page url and paste it back on the console.\n The method will then return True if the login attempt was succesful.\n \n - When authenticating with your own identity:\n \n 1. Instantiate an `Account` object with the credentials (client id and client secret), specifying the parameter `auth_flow_type` to *\"credentials\"*. You also need to provide a 'tenant_id'. You don't need to specify any scopes.\n 1. Call `account.authenticate`. This call will request a token for you and store it in the backend. No user interaction is needed. The method will store the token in the backend and return True if the authentication succeeded.\n \n For Example:\n ```python\n from O365 import Account\n \n credentials = ('my_client_id', 'my_client_secret')\n \n # the default protocol will be Microsoft Graph\n \n account = Account(credentials, auth_flow_type='credentials', tenant_id='my-tenant-id')\n if account.authenticate():\n print('Authenticated!')\n ```\n \n1. At this point you will have an access token stored that will provide valid credentials when using the api. \n\n The access token only lasts **60 minutes**, but the app try will automatically request new access tokens.\n \n When using the \"on behalf of a user\" authentication method this is accomplished through the refresh tokens (if and only if you added the \"offline_access\" permission), but note that a refresh token only lasts for 90 days. So you must use it before or you will need to request a new access token again (no new consent needed by the user, just a login).\n If your application needs to work for more than 90 days without user interaction and without interacting with the API, then you must implement a periodic call to `Connection.refresh_token` before the 90 days have passed.\n \n **Take care: the access (and refresh) token must remain protected from unauthorized users.**\n \n Under the \"on behalf of a user\" authentication method, if you change the scope requested, then the current token won't work, and you will need the user to give consent again on the application to gain access to the new scopes requested.\n\n \n##### Different Authentication Interfaces\n\nTo acomplish the authentication you can basically use different approaches.\nThe following apply to the \"on behalf of a user\" authentication method as this is 2-step authentication flow.\nFor the \"with your own identity\" authentication method, you can just use `account.authenticate` as it's not going to require a console input.\n\n1. Console based authentication interface:\n\n You can authenticate using a console. The best way to achieve this is by using the `authenticate` method of the `Account` class.\n \n ```python\n account = Account(credentials)\n account.authenticate(scopes=['basic', 'message_all'])\n ```\n \n The `authenticate` method will print into the console a url that you will have to visit to achieve authentication.\n Then after visiting the link and authenticate you will have to paste back the resulting url into the console.\n The method will return `True` and print a message if it was succesful.\n \n **Tip:** When using MacOs the console is limited to 1024 characters. If your url has multiple scopes it can exceed this limit. To solve this. Just `import readline` a the top of your script.\n \n1. Web app based authentication interface:\n\n You can authenticate your users in a web environment by following this steps:\n \n 1. First ensure you are using an appropiate TokenBackend to store the auth tokens (See Token storage below).\n 1. From a handler redirect the user to the Microsoft login url. Provide a callback. Store the state.\n 1. From the callback handler complete the authentication with the state and other data.\n \n The following example is done using Flask.\n ```python \n @route('/stepone')\n def auth_step_one():\n \n callback = 'my absolute url to auth_step_two_callback'\n account = Account(credentials)\n url, state = account.con.get_authorization_url(requested_scopes=my_scopes,\n redirect_uri=callback)\n \n # the state must be saved somewhere as it will be needed later\n my_db.store_state(state) # example...\n \n return redirect(url)\n \n @route('/steptwo')\n def auth_step_two_callback():\n account = Account(credentials)\n \n # retreive the state saved in auth_step_one\n my_saved_state = my_db.get_state() # example...\n \n # rebuild the redirect_uri used in auth_step_one\n callback = 'my absolute url to auth_step_two_callback'\n \n result = account.con.request_token(request.url, \n state=my_saved_state,\n redirect_uri=callback)\n # if result is True, then authentication was succesful \n # and the auth token is stored in the token backend\n if result:\n return render_template('auth_complete.html')\n # else ....\n ``` \n\n1. Other authentication interfaces:\n\n Finally you can configure any other flow by using `connection.get_authorization_url` and `connection.request_token` as you want.\n \n\n##### Permissions and Scopes:\n\n###### Permissions\n\nWhen using oauth, you create an application and allow some resources to be accessed and used by its users.\nThese resources are managed with permissions. These can either be delegated (on behalf of a user) or aplication permissions.\nThe former are used when the authentication method is \"on behalf of a user\". Some of these require administrator consent. \nThe latter when using the \"with your own identity\" authentication method. All of these require administrator consent.\n\n###### Scopes\n\nThe scopes only matter when using the \"on behalf of a user\" authentication method.\n\n> Note: You only need the scopes when login as those are kept stored within the token on the token backend.\n\nThe user of this library can then request access to one or more of this resources by providing scopes to the oauth provider.\n\n> Note: If you latter on change the scopes requested, the current token will be invaled and you will have to re-authenticate. The user that logins will be asked for consent.\n\nFor example your application can have Calendar.Read, Mail.ReadWrite and Mail.Send permissions, but the application can request access only to the Mail.ReadWrite and Mail.Send permission.\nThis is done by providing scopes to the `Account` instance or `account.authenticate` method like so:\n\n```python\nfrom O365 import Account\n\ncredentials = ('client_id', 'client_secret')\n\nscopes = ['https://graph.microsoft.com/Mail.ReadWrite', 'https://graph.microsoft.com/Mail.Send']\n\naccount = Account(credentials, scopes=scopes)\naccount.authenticate()\n\n# The latter is exactly the same as passing scopes to the authenticate method like so:\n# account = Account(credentials)\n# account.authenticate(scopes=scopes)\n```\n\nScope implementation depends on the protocol used. So by using protocol data you can automatically set the scopes needed.\nThis is implemented by using 'scope helpers'. Those are little helpers that group scope functionallity and abstract the procotol used.\n\nScope Helper | Scopes included\n:--- | :--- \nbasic | 'offline_access' and 'User.Read' \nmailbox | 'Mail.Read'\nmailbox_shared | 'Mail.Read.Shared'\nmessage_send | 'Mail.Send'\nmessage_send_shared | 'Mail.Send.Shared'\nmessage_all | 'Mail.ReadWrite' and 'Mail.Send'\nmessage_all_shared | 'Mail.ReadWrite.Shared' and 'Mail.Send.Shared'\naddress_book | 'Contacts.Read'\naddress_book_shared | 'Contacts.Read.Shared'\naddress_book_all | 'Contacts.ReadWrite'\naddress_book_all_shared | 'Contacts.ReadWrite.Shared'\ncalendar | 'Calendars.Read'\ncalendar_shared | 'Calendars.Read.Shared'\ncalendar_all | 'Calendars.ReadWrite'\ncalendar_shared_all | 'Calendars.ReadWrite.Shared'\ntasks | 'Tasks.Read'\ntasks_all | 'Tasks.ReadWrite'\nusers | 'User.ReadBasic.All'\nonedrive | 'Files.Read.All'\nonedrive_all | 'Files.ReadWrite.All'\nsharepoint | 'Sites.Read.All'\nsharepoint_dl | 'Sites.ReadWrite.All'\n\n\nYou can get the same scopes as before using protocols and scope helpers like this:\n\n```python\nprotocol_graph = MSGraphProtocol()\n\nscopes_graph = protocol.get_scopes_for('message all')\n# scopes here are: ['https://graph.microsoft.com/Mail.ReadWrite', 'https://graph.microsoft.com/Mail.Send']\n\naccount = Account(credentials, scopes=scopes_graph)\n```\n\n```python\nprotocol_office = MSOffice365Protocol()\n\nscopes_office = protocol.get_scopes_for('message all')\n# scopes here are: ['https://outlook.office.com/Mail.ReadWrite', 'https://outlook.office.com/Mail.Send']\n\naccount = Account(credentials, scopes=scopes_office)\n```\n\n> Note: When passing scopes at the `Account` initialization or on the `account.authenticate` method, the scope helpers are autommatically converted to the protocol flavor.\n>Those are the only places where you can use scope helpers. Any other object using scopes (such as the `Connection` object) expects scopes that are already set for the protocol. \n\n\n\n##### Token storage:\nWhen authenticating you will retrieve oauth tokens. If you don't want a one time access you will have to store the token somewhere.\nO365 makes no assumptions on where to store the token and tries to abstract this from the library usage point of view.\n\nYou can choose where and how to store tokens by using the proper Token Backend.\n\n**Take care: the access (and refresh) token must remain protected from unauthorized users.**\n\nThe library will call (at different stages) the token backend methods to load and save the token.\n\nMethods that load tokens:\n- `account.is_authenticated` property will try to load the token if is not already loaded.\n- `connection.get_session`: this method is called when there isn't a request session set. By default it will not try to load the token. Set `load_token=True` to load it.\n\nMethods that stores tokens:\n- `connection.request_token`: by default will store the token, but you can set `store_token=False` to avoid it.\n- `connection.refresh_token`: by default will store the token. To avoid it change `connection.store_token` to False. This however it's a global setting (that only affects the `refresh_token` method). If you only want the next refresh operation to not store the token you will have to set it back to True afterwards. \n\nTo store the token you will have to provide a properly configured TokenBackend.\n\nActually there are only two implemented (but you can easely implement more like a CookieBackend, RedisBackend, etc.):\n- `FileSystemTokenBackend` (Default backend): Stores and retrieves tokens from the file system. Tokens are stored as files.\n- `FirestoreTokenBackend`: Stores and retrives tokens from a Google Firestore Datastore. Tokens are stored as documents within a collection.\n\nFor example using the FileSystem Token Backend:\n\n```python\nfrom O365 import Account, FileSystemTokenBackend\n\ncredentials = ('id', 'secret')\n\n# this will store the token under: \"my_project_folder/my_folder/my_token.txt\".\n# you can pass strings to token_path or Path instances from pathlib\ntoken_backend = FileSystemTokenBackend(token_path='my_folder', token_filename='my_token.txt')\naccount = Account(credentials, token_backend=token_backend)\n\n# This account instance tokens will be stored on the token_backend configured before.\n# You don't have to do anything more\n# ...\n```\n\nAnd now using the same example using FirestoreTokenBackend:\n\n```python\nfrom O365 import Account\nfrom O365.utils import FirestoreBackend\nfrom google.cloud import firestore\n\ncredentials = ('id', 'secret')\n\n# this will store the token on firestore under the tokens collection on the defined doc_id.\n# you can pass strings to token_path or Path instances from pathlib\nuser_id = 'whatever the user id is' # used to create the token document id\ndocument_id = 'token_{}'.format(user_id) # used to uniquely store this token\ntoken_backend = FirestoreBackend(client=firestore.Client(), collection='tokens', doc_id=document_id)\naccount = Account(credentials, token_backend=token_backend)\n\n# This account instance tokens will be stored on the token_backend configured before.\n# You don't have to do anything more\n# ...\n```\n\nTo implement a new TokenBackend:\n \n 1. Subclass `BaseTokenBackend`\n 1. Implement the following methods:\n \n - `__init__` (don't forget to call `super().__init__`)\n - `load_token`: this should load the token from the desired backend and return a `Token` instance or None\n - `save_token`: this should store the `self.token` in the desired backend.\n - Optionally you can implement: `check_token`, `delete_token` and `should_refresh_token`\n \nThe `should_refresh_token` method is intended to be implemented for environments where multiple Connection instances are running on paralel.\nThis method should check if it's time to refresh the token or not.\nThe chosen backend can store a flag somewhere to answer this question.\nThis can avoid race conditions between different instances trying to refresh the token at once, when only one should make the refresh.\nThe method should return three posible values:\n- **True**: then the Connection will refresh the token.\n- **False**: then the Connection will NOT refresh the token.\n- **None**: then this method already executed the refresh and therefore the Connection does not have to.\n\nBy default this always returns True as it's asuming there is are no parallel connections running at once.\n\nThere are two examples of this method in the examples folder [here](https://github.com/O365/python-o365/blob/master/examples/token_backends.py).\n\n\n## Protocols\nProtocols handles the aspects of communications between different APIs.\nThis project uses either the Microsoft Graph APIs (by default) or the Office 365 APIs.\nBut, you can use many other Microsoft APIs as long as you implement the protocol needed.\n\nYou can use one or the other:\n\n- `MSGraphProtocol` to use the [Microsoft Graph API](https://developer.microsoft.com/en-us/graph/docs/concepts/overview)\n- `MSOffice365Protocol` to use the [Office 365 API](https://msdn.microsoft.com/en-us/office/office365/api/api-catalog)\n\nBoth protocols are similar but consider the following:\n\nReasons to use `MSGraphProtocol`:\n- It is the recommended Protocol by Microsoft.\n- It can access more resources over Office 365 (for example OneDrive)\n\nReasons to use `MSOffice365Protocol`:\n- It can send emails with attachments up to 150 MB. MSGraph only allows 4MB on each request (UPDATE: Starting 22 October'19 you can [upload files up to 150MB with MSGraphProtocol **beta** version](https://developer.microsoft.com/en-us/office/blogs/attaching-large-files-to-outlook-messages-in-microsoft-graph-preview/))\n\nThe default protocol used by the `Account` Class is `MSGraphProtocol`.\n\nYou can implement your own protocols by inheriting from `Protocol` to communicate with other Microsoft APIs.\n\nYou can instantiate and use protocols like this:\n```python\nfrom O365 import Account, MSGraphProtocol # same as from O365.connection import MSGraphProtocol\n\n# ...\n\n# try the api version beta of the Microsoft Graph endpoint.\nprotocol = MSGraphProtocol(api_version='beta') # MSGraphProtocol defaults to v1.0 api version\naccount = Account(credentials, protocol=protocol)\n```\n\n##### Resources:\nEach API endpoint requires a resource. This usually defines the owner of the data.\nEvery protocol defaults to resource 'ME'. 'ME' is the user which has given consent, but you can change this behaviour by providing a different default resource to the protocol constructor.\n\n> Note: When using the \"with your own identity\" authentication method the resource 'ME' is overwritten to be blank as the authentication method already states that you are login with your own identity.\n\nFor example when accessing a shared mailbox:\n\n\n```python\n# ...\naccount = Account(credentials=my_credentials, main_resource='shared_mailbox@example.com')\n# Any instance created using account will inherit the resource defined for account.\n```\n\nThis can be done however at any point. For example at the protocol level:\n```python\n# ...\nprotocol = MSGraphProtocol(default_resource='shared_mailbox@example.com')\n\naccount = Account(credentials=my_credentials, protocol=protocol)\n\n# now account is accesing the shared_mailbox@example.com in every api call.\nshared_mailbox_messages = account.mailbox().get_messages()\n```\n \nInstead of defining the resource used at the account or protocol level, you can provide it per use case as follows:\n```python\n# ...\naccount = Account(credentials=my_credentials) # account defaults to 'ME' resource\n\nmailbox = account.mailbox('shared_mailbox@example.com') # mailbox is using 'shared_mailbox@example.com' resource instead of 'ME'\n\n# or:\n\nmessage = Message(parent=account, main_resource='shared_mailbox@example.com') # message is using 'shared_mailbox@example.com' resource\n```\n\nUsually you will work with the default 'ME' resource, but you can also use one of the following:\n\n- **'me'**: the user which has given consent. the default for every protocol. Overwritten when using \"with your own identity\" authentication method (Only available on the authorization auth_flow_type). \n- **'user:user@domain.com'**: a shared mailbox or a user account for which you have permissions. If you don't provide 'user:' will be infered anyways.\n- **'site:sharepoint-site-id'**: a sharepoint site id.\n- **'group:group-site-id'**: a office365 group id.\n\nBy setting the resource prefix (such as **'user:'** or **'group:'**) you help the library understand the type of resource. You can also pass it like 'users/example@exampl.com'. Same applies to the other resource prefixes.\n\n\n## Account Class and Modularity \nUsually you will only need to work with the `Account` Class. This is a wrapper around all functionality.\n\nBut you can also work only with the pieces you want.\n\nFor example, instead of:\n```python\nfrom O365 import Account\n\naccount = Account(('client_id', 'client_secret'))\nmessage = account.new_message()\n# ...\nmailbox = account.mailbox()\n# ...\n```\n\nYou can work only with the required pieces:\n\n```python\nfrom O365 import Connection, MSGraphProtocol\nfrom O365.message import Message\nfrom O365.mailbox import MailBox\n\nprotocol = MSGraphProtocol()\nscopes = ['...']\ncon = Connection(('client_id', 'client_secret'), scopes=scopes)\n\nmessage = Message(con=con, protocol=protocol)\n# ...\nmailbox = MailBox(con=con, protocol=protocol)\nmessage2 = Message(parent=mailbox) # message will inherit the connection and protocol from mailbox when using parent.\n# ...\n```\n\nIt's also easy to implement a custom Class.\n\nJust Inherit from `ApiComponent`, define the endpoints, and use the connection to make requests. If needed also inherit from Protocol to handle different comunications aspects with the API server.\n\n```python\nfrom O365.utils import ApiComponent \n\nclass CustomClass(ApiComponent):\n _endpoints = {'my_url_key': '/customendpoint'}\n \n def __init__(self, *, parent=None, con=None, **kwargs):\n # connection is only needed if you want to communicate with the api provider\n self.con = parent.con if parent else con\n protocol = parent.protocol if parent else kwargs.get('protocol')\n main_resource = parent.main_resource\n \n super().__init__(protocol=protocol, main_resource=main_resource)\n # ...\n\n def do_some_stuff(self):\n \n # self.build_url just merges the protocol service_url with the enpoint passed as a parameter\n # to change the service_url implement your own protocol inherinting from Protocol Class\n url = self.build_url(self._endpoints.get('my_url_key')) \n \n my_params = {'param1': 'param1'}\n\n response = self.con.get(url, params=my_params) # note the use of the connection here.\n\n # handle response and return to the user...\n\n# the use it as follows:\nfrom O365 import Connection, MSGraphProtocol\n\nprotocol = MSGraphProtocol() # or maybe a user defined protocol\ncon = Connection(('client_id', 'client_secret'), scopes=protocol.get_scopes_for(['...']))\ncustom_class = CustomClass(con=con, protocol=protocol)\n\ncustom_class.do_some_stuff()\n```\n\n## MailBox\nMailbox groups the funcionality of both the messages and the email folders.\n\nThese are the scopes needed to work with the `MailBox` and `Message` classes.\n\n Raw Scope | Included in Scope Helper | Description\n :---: | :---: | ---\n *Mail.Read* | *mailbox* | To only read my mailbox\n *Mail.Read.Shared* | *mailbox_shared* | To only read another user / shared mailboxes\n *Mail.Send* | *message_send, message_all* | To only send message\n *Mail.Send.Shared* | *message_send_shared, message_all_shared* | To only send message as another user / shared mailbox\n *Mail.ReadWrite* | *message_all* | To read and save messages in my mailbox\n *Mail.ReadWrite.Shared* | *message_all_shared* | To read and save messages in another user / shared mailbox\n\n```python\nmailbox = account.mailbox()\n\ninbox = mailbox.inbox_folder()\n\nfor message in inbox.get_messages():\n print(message)\n\nsent_folder = mailbox.sent_folder()\n\nfor message in sent_folder.get_messages():\n print(message)\n\nm = mailbox.new_message()\n\nm.to.add('to_example@example.com')\nm.body = 'George Best quote: In 1969 I gave up women and alcohol - it was the worst 20 minutes of my life.'\nm.save_draft()\n```\n\n#### Email Folder\nRepresents a `Folder` within your email mailbox.\n\nYou can get any folder in your mailbox by requesting child folders or filtering by name.\n\n```python\nmailbox = account.mailbox()\n\narchive = mailbox.get_folder(folder_name='archive') # get a folder with 'archive' name\n\nchild_folders = archive.get_folders(25) # get at most 25 child folders of 'archive' folder\n\nfor folder in child_folders:\n print(folder.name, folder.parent_id)\n\nnew_folder = archive.create_child_folder('George Best Quotes')\n```\n\n#### Message\nAn email object with all it's data and methods.\n\nCreating a draft message is as easy as this:\n```python\nmessage = mailbox.new_message()\nmessage.to.add(['example1@example.com', 'example2@example.com'])\nmessage.sender.address = 'my_shared_account@example.com' # changing the from address\nmessage.body = 'George Best quote: I might go to Alcoholics Anonymous, but I think it would be difficult for me to remain anonymous'\nmessage.attachments.add('george_best_quotes.txt')\nmessage.save_draft() # save the message on the cloud as a draft in the drafts folder\n```\n\nWorking with saved emails is also easy:\n```python\nquery = mailbox.new_query().on_attribute('subject').contains('george best') # see Query object in Utils\nmessages = mailbox.get_messages(limit=25, query=query)\n\nmessage = messages[0] # get the first one\n\nmessage.mark_as_read()\nreply_msg = message.reply()\n\nif 'example@example.com' in reply_msg.to: # magic methods implemented\n reply_msg.body = 'George Best quote: I spent a lot of money on booze, birds and fast cars. The rest I just squandered.'\nelse:\n reply_msg.body = 'George Best quote: I used to go missing a lot... Miss Canada, Miss United Kingdom, Miss World.'\n\nreply_msg.send()\n```\n\n##### Sending Inline Images\nYou can send inline images by doing this:\n\n```python\n# ...\nmsg = account.new_message()\nmsg.to.add('george@best.com')\nmsg.attachments.add('my_image.png')\natt = msg.attachments[0] # get the attachment object\n\n# this is super important for this to work.\natt.is_inline = True\natt.content_id = 'image.png'\n\n# notice we insert an image tag with source to: \"cid:{content_id}\"\nbody = \"\"\"\n \n
\n There should be an image here:\n\n
\n