├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── microsoftgraph ├── __init__.py ├── calendar.py ├── client.py ├── contacts.py ├── decorators.py ├── exceptions.py ├── files.py ├── mail.py ├── notes.py ├── response.py ├── users.py ├── utils.py ├── webhooks.py └── workbooks.py └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 GearPlug 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microsoftgraph-python 2 | Microsoft graph API wrapper for Microsoft Graph written in Python. 3 | 4 | ## Before start 5 | To use Microsoft Graph to read and write resources on behalf of a user, your app must get an access token from 6 | the Microsoft identity platform and attach the token to requests that it sends to Microsoft Graph. The exact 7 | authentication flow that you will use to get access tokens will depend on the kind of app you are developing and 8 | whether you want to use OpenID Connect to sign the user in to your app. One common flow used by native and mobile 9 | apps and also by some Web apps is the OAuth 2.0 authorization code grant flow. 10 | 11 | See [Get access on behalf of a user](https://docs.microsoft.com/en-us/graph/auth-v2-user) 12 | 13 | ## Breaking changes if you're upgrading prior 1.0.0 14 | - Added structure to library to match API documentation for e.g. `client.get_me()` => `client.users.get_me()`. 15 | - Renamed several methods to match API documentation for e.g. `client.get_me_events()` => `client.calendar.list_events()`. 16 | - Result from calling a method is not longer a dictionary but a Response object. To access the dict response as before then call `.data` attribute for e.g `r = client.users.get_me()` then `r.data`. 17 | - Previous API calls made through beta endpoints are now pointing to v1.0 by default. This can be changed to beta if needed with the parameter `api_version` in the client instantiation. 18 | - Removed Office 365 endpoints as they were merged with the Microsoft Graph API. See [Office 365 APIs](https://docs.microsoft.com/en-us/previous-versions/office/office-365-api/). 19 | ## New in 1.0.0 20 | - You can access to [Requests library's Response Object](https://docs.python-requests.org/en/latest/user/advanced/#request-and-response-objects) for e.g. `r = client.users.get_me()` then `r.original` or the response handled by the library `r.data`. 21 | - New Response properties `r.status_code` and `r.throttling`. 22 | - You can pass [Requests library's Event Hooks](https://docs.python-requests.org/en/latest/user/advanced/#event-hooks) with the parameter `requests_hooks` in the client instantiation. If you are using Django and want to log in database every request made through this library, see [django-requests-logger](https://github.com/GearPlug/django-requests-logger). 23 | - Library can auto paginate responses. Set `paginate` parameter in client initialization. Defaults to `True`. 24 | - Better method docstrings and type hinting. 25 | - Better library structure. 26 | ## Installing 27 | ``` 28 | pip install microsoftgraph-python 29 | ``` 30 | ## Usage 31 | ### Client instantiation 32 | ``` 33 | from microsoftgraph.client import Client 34 | client = Client('CLIENT_ID', 'CLIENT_SECRET', account_type='common') # by default common, thus account_type is optional parameter. 35 | ``` 36 | 37 | ### OAuth 2.0 38 | #### Get authorization url 39 | ``` 40 | url = client.authorization_url(redirect_uri, scope, state=None) 41 | ``` 42 | 43 | #### Exchange the code for an access token 44 | ``` 45 | response = client.exchange_code(redirect_uri, code) 46 | ``` 47 | 48 | #### Refresh token 49 | ``` 50 | response = client.refresh_token(redirect_uri, refresh_token) 51 | ``` 52 | 53 | #### Set token 54 | ``` 55 | client.set_token(token) 56 | ``` 57 | 58 | ### Users 59 | #### Get me 60 | ``` 61 | response = client.users.get_me() 62 | ``` 63 | 64 | ### Mail 65 | 66 | #### List messages 67 | ``` 68 | response = client.mail.list_messages() 69 | ``` 70 | #### Get message 71 | ``` 72 | response = client.mail.get_message(message_id) 73 | ``` 74 | 75 | #### Send mail 76 | ``` 77 | data = { 78 | subject="Meet for lunch?", 79 | content="The new cafeteria is open.", 80 | content_type="text", 81 | to_recipients=["fannyd@contoso.onmicrosoft.com"], 82 | cc_recipients=None, 83 | save_to_sent_items=True, 84 | } 85 | response = client.mail.send_mail(**data) 86 | ``` 87 | 88 | #### List mail folders 89 | ``` 90 | response = client.mail.list_mail_folders() 91 | ``` 92 | 93 | #### Create mail folder 94 | ``` 95 | response = client.mail.create_mail_folder(display_name) 96 | ``` 97 | 98 | ### Notes 99 | #### List notebooks 100 | ``` 101 | response = client.notes.list_notebooks() 102 | ``` 103 | 104 | #### Get notebook 105 | ``` 106 | response = client.notes.get_notebook(notebook_id) 107 | ``` 108 | 109 | #### Get notebook sections 110 | ``` 111 | response = client.notes.list_sections(notebook_id) 112 | ``` 113 | 114 | #### List pages 115 | ``` 116 | response = client.notes.list_pages() 117 | ``` 118 | 119 | #### Create page 120 | ``` 121 | response = client.notes.create_page(section_id, files) 122 | ``` 123 | 124 | ### Calendar 125 | #### Get events 126 | ``` 127 | response = client.calendar.list_events(calendar_id) 128 | ``` 129 | 130 | #### Get event 131 | ``` 132 | response = client.calendar.get_event(event_id) 133 | ``` 134 | 135 | #### Create calendar event 136 | ``` 137 | from datetime import datetime, timedelta 138 | 139 | start_datetime = datetime.now() + timedelta(days=1) # tomorrow 140 | end_datetime = datetime.now() + timedelta(days=1, hours=1) # tomorrow + one hour 141 | timezone = "America/Bogota" 142 | 143 | data = { 144 | "calendar_id": "CALENDAR_ID", 145 | "subject": "Let's go for lunch", 146 | "content": "Does noon work for you?", 147 | "content_type": "text", 148 | "start_datetime": start_datetime, 149 | "start_timezone": timezone, 150 | "end_datetime": end_datetime, 151 | "end_timezone": timezone, 152 | "location": "Harry's Bar", 153 | } 154 | response = client.calendar.create_event(**data) 155 | ``` 156 | 157 | #### Get calendars 158 | ``` 159 | response = client.calendar.list_calendars() 160 | ``` 161 | 162 | #### Create calendar 163 | ``` 164 | response = client.calendar.create_calendar(name) 165 | ``` 166 | 167 | ### Contacts 168 | #### Get a contact 169 | ``` 170 | response = client.contacts.get_contact(contact_id) 171 | ``` 172 | 173 | #### Get contacts 174 | ``` 175 | response = client.contacts.list_contacts() 176 | ``` 177 | 178 | #### Create contact 179 | ``` 180 | data = { 181 | "given_name": "Pavel", 182 | "surname": "Bansky", 183 | "email_addresses": [ 184 | { 185 | "address": "pavelb@fabrikam.onmicrosoft.com", 186 | "name": "Pavel Bansky" 187 | } 188 | ], 189 | "business_phones": [ 190 | "+1 732 555 0102" 191 | ], 192 | "folder_id": None, 193 | } 194 | response = client.contacts.create_contact(**data) 195 | ``` 196 | 197 | #### Get contact folders 198 | ``` 199 | response = client.contacts.list_contact_folders() 200 | ``` 201 | 202 | #### Create contact folders 203 | ``` 204 | response = client.contacts.create_contact_folder() 205 | ``` 206 | 207 | ### Files 208 | #### Get root items 209 | ``` 210 | response = client.files.drive_root_items() 211 | ``` 212 | 213 | #### Get root children items 214 | ``` 215 | response = client.files.drive_root_children_items() 216 | ``` 217 | 218 | #### Get specific folder items 219 | ``` 220 | response = client.files.drive_specific_folder(folder_id) 221 | ``` 222 | 223 | #### Get item 224 | ``` 225 | response = client.files.drive_get_item(item_id) 226 | ``` 227 | 228 | #### Download the contents of a specific item 229 | ``` 230 | response = client.files.drive_download_contents(item_id) 231 | ``` 232 | 233 | #### Upload new file 234 | ``` 235 | # This example uploads the image in path to a file in the signed-in user's drive under Pictures named upload.jpg. 236 | response = client.files.drive_upload_new_file("/Pictures/upload.jpg", "/mnt/c/Users/i/Downloads/image1.jpg") 237 | ``` 238 | 239 | #### Update existing file 240 | ``` 241 | # This example uploads the image in path to update an existing item id. 242 | response = client.files.drive_update_existing_file(item_id, "/mnt/c/Users/i/Downloads/image2.jpg") 243 | ``` 244 | 245 | #### Search for files 246 | ``` 247 | query = ".xlsx, .xlsm" 248 | response = client.files.search_items(query) 249 | ``` 250 | 251 | ### Workbooks 252 | #### Create session for specific item 253 | ``` 254 | response = client.workbooks.create_session(workbook_id) 255 | ``` 256 | 257 | #### Refresh session for specific item 258 | ``` 259 | response = client.workbooks.refresh_session(workbook_id) 260 | ``` 261 | 262 | #### Close session for specific item 263 | ``` 264 | response = client.workbooks.close_session(workbook_id) 265 | ``` 266 | 267 | #### Get worksheets 268 | ``` 269 | response = client.workbooks.list_worksheets(workbook_id) 270 | ``` 271 | 272 | #### Get specific worksheet 273 | ``` 274 | response = client.workbooks.get_worksheet(workbook_id, worksheet_id) 275 | ``` 276 | 277 | #### Add worksheets 278 | ``` 279 | response = client.workbooks.add_worksheet(workbook_id) 280 | ``` 281 | 282 | #### Update worksheet 283 | ``` 284 | response = client.workbooks.update_worksheet(workbook_id, worksheet_id) 285 | ``` 286 | 287 | #### Get charts 288 | ``` 289 | response = client.workbooks.list_charts(workbook_id, worksheet_id) 290 | ``` 291 | 292 | #### Add chart 293 | ``` 294 | response = client.workbooks.add_chart(workbook_id, worksheet_id) 295 | ``` 296 | 297 | #### Get tables 298 | ``` 299 | response = client.workbooks.list_tables(workbook_id) 300 | ``` 301 | 302 | #### Add table 303 | ``` 304 | response = client.workbooks.add_table(workbook_id) 305 | ``` 306 | 307 | #### Add column to table 308 | ``` 309 | response = client.workbooks.create_table_column(workbook_id, worksheet_id, table_id) 310 | ``` 311 | 312 | #### Add row to table 313 | ``` 314 | response = client.workbooks.create_table_row(workbook_id, worksheet_id, table_id) 315 | ``` 316 | 317 | #### Get table rows 318 | ``` 319 | response = client.workbooks.list_table_rows(workbook_id, table_id) 320 | ``` 321 | 322 | #### Get range 323 | ``` 324 | response = client.workbooks.get_range(workbook_id, worksheet_id) 325 | ``` 326 | 327 | #### Get used range 328 | ``` 329 | response = client.workbooks.get_used_range(workbook_id, worksheet_id) 330 | ``` 331 | 332 | #### Update range 333 | ``` 334 | response1 = client.workbooks.create_session(workbook_id) 335 | workbook_session_id = response1.data["id"] 336 | 337 | client.set_workbook_session_id(workbook_session_id) 338 | 339 | range_address = "A1:D2" 340 | data = { 341 | "values": [ 342 | ["John", "Doe", "+1 305 1234567", "Miami, FL"], 343 | ["Bill", "Gates", "+1 305 1234567", "St. Redmond, WA"], 344 | ] 345 | } 346 | response2 = client.workbooks.update_range(workbook_id, worksheet_id, range_address, json=data) 347 | 348 | response3 = client.worbooks.close_session(workbook_id) 349 | ``` 350 | 351 | ### Webhooks 352 | #### Create subscription 353 | ``` 354 | response = client.webhooks.create_subscription(change_type, notification_url, resource, expiration_datetime, client_state=None) 355 | ``` 356 | 357 | #### Renew subscription 358 | ``` 359 | response = client.webhooks.renew_subscription(subscription_id, expiration_datetime) 360 | ``` 361 | 362 | #### Delete subscription 363 | ``` 364 | response = client.webhooks.delete_subscription(subscription_id) 365 | ``` 366 | 367 | 368 | ## Requirements 369 | - requests 370 | 371 | ## Tests 372 | ``` 373 | test/test.py 374 | ``` 375 | -------------------------------------------------------------------------------- /microsoftgraph/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GearPlug/microsoftgraph-python/0a0e5bb77d3f91d037235884a960c91a9c67c1d3/microsoftgraph/__init__.py -------------------------------------------------------------------------------- /microsoftgraph/calendar.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from microsoftgraph.decorators import token_required 4 | from microsoftgraph.response import Response 5 | from microsoftgraph.utils import format_time 6 | 7 | 8 | class Calendar(object): 9 | def __init__(self, client) -> None: 10 | """Working with Outlook Calendar. 11 | 12 | https://docs.microsoft.com/en-us/graph/api/resources/calendar?view=graph-rest-1.0 13 | 14 | Args: 15 | client (Client): Library Client. 16 | """ 17 | self._client = client 18 | 19 | @token_required 20 | def list_events(self, calendar_id: str = None, params: dict = None) -> Response: 21 | """Get a list of event objects in the user's mailbox. The list contains single instance meetings and series 22 | masters. 23 | 24 | https://docs.microsoft.com/en-us/graph/api/user-list-events?view=graph-rest-1.0&tabs=http 25 | 26 | Args: 27 | calendar_id (str): Calendar ID. 28 | params (dict, optional): Query. Defaults to None. 29 | 30 | Returns: 31 | Response: Microsoft Graph Response. 32 | """ 33 | url = "me/calendars/{}/events".format(calendar_id) if calendar_id else "me/events" 34 | return self._client._get(self._client.base_url + url, params=params) 35 | 36 | @token_required 37 | def get_event(self, event_id: str, params: dict = None) -> Response: 38 | """Get the properties and relationships of the specified event object. 39 | 40 | https://docs.microsoft.com/en-us/graph/api/event-get?view=graph-rest-1.0&tabs=http 41 | 42 | Args: 43 | event_id (str): Event ID. 44 | params (dict, optional): Query. Defaults to None. 45 | 46 | Returns: 47 | Response: Microsoft Graph Response. 48 | """ 49 | return self._client._get(self._client.base_url + "me/events/{}".format(event_id), params=params) 50 | 51 | @token_required 52 | def create_event( 53 | self, 54 | subject: str, 55 | content: str, 56 | start_datetime: datetime, 57 | start_timezone: str, 58 | end_datetime: datetime, 59 | end_timezone: str, 60 | location: str, 61 | calendar_id: str = None, 62 | content_type: str = "HTML", 63 | attendees: list = None, 64 | is_online_meeting: bool = False, 65 | online_meeting_provider: str = 'teamsForBusiness', 66 | **kwargs, 67 | ) -> Response: 68 | """Create an event in the user's default calendar or specified calendar. 69 | 70 | https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http 71 | 72 | Additional time zones: https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0 73 | 74 | Args: 75 | subject (str): The text of the event's subject line. 76 | content (str): The body of the message associated with the event. 77 | start_datetime (datetime): A single point of time in a combined date and time representation ({date}T{time}; 78 | start_timezone (str): Represents a time zone, for example, "Pacific Standard Time". 79 | end_datetime (datetime): A single point of time in a combined date and time representation ({date}T{time}; for 80 | end_timezone (str): Represents a time zone, for example, "Pacific Standard Time". 81 | location (str): The location of the event. 82 | calendar_id (str, optional): Calendar ID. Defaults to None. 83 | content_type (str, optional): It can be in HTML or text format. Defaults to HTML. 84 | 85 | Returns: 86 | Response: Microsoft Graph Response. 87 | """ 88 | if isinstance(start_datetime, datetime): 89 | start_datetime = format_time(start_datetime) 90 | if isinstance(end_datetime, datetime): 91 | end_datetime = format_time(end_datetime) 92 | 93 | body = { 94 | "subject": subject, 95 | "body": { 96 | "contentType": content_type, 97 | "content": content, 98 | }, 99 | "start": { 100 | "dateTime": start_datetime, 101 | "timeZone": start_timezone, 102 | }, 103 | "end": { 104 | "dateTime": end_datetime, 105 | "timeZone": end_timezone, 106 | }, 107 | "location": {"displayName": location}, 108 | "isOnlineMeeting": is_online_meeting, 109 | } 110 | 111 | if is_online_meeting: 112 | body["onlineMeetingProvider"] = online_meeting_provider 113 | 114 | if attendees: 115 | body["attendees"] = attendees 116 | 117 | body.update(kwargs) 118 | url = "me/calendars/{}/events".format(calendar_id) if calendar_id is not None else "me/events" 119 | return self._client._post(self._client.base_url + url, json=body) 120 | 121 | @token_required 122 | def list_calendars(self, params: dict = None) -> Response: 123 | """Get all the user's calendars (/calendars navigation property), get the calendars from the default calendar 124 | group or from a specific calendar group. 125 | 126 | https://docs.microsoft.com/en-us/graph/api/user-list-calendars?view=graph-rest-1.0&tabs=http 127 | 128 | Args: 129 | params (dict, optional): Query. Defaults to None. 130 | 131 | Returns: 132 | Response: Microsoft Graph Response. 133 | """ 134 | return self._client._get(self._client.base_url + "me/calendars", params=params) 135 | 136 | @token_required 137 | def create_calendar(self, name: str) -> Response: 138 | """Create a new calendar for a user. 139 | 140 | https://docs.microsoft.com/en-us/graph/api/user-post-calendars?view=graph-rest-1.0&tabs=http 141 | 142 | Args: 143 | name (str): The calendar name. 144 | 145 | Returns: 146 | Response: Microsoft Graph Response. 147 | """ 148 | body = {"name": "{}".format(name)} 149 | return self._client._post(self._client.base_url + "me/calendars", json=body) 150 | -------------------------------------------------------------------------------- /microsoftgraph/client.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from urllib.parse import urlencode 3 | 4 | import requests 5 | 6 | from microsoftgraph import exceptions 7 | from microsoftgraph.calendar import Calendar 8 | from microsoftgraph.contacts import Contacts 9 | from microsoftgraph.files import Files 10 | from microsoftgraph.mail import Mail 11 | from microsoftgraph.notes import Notes 12 | from microsoftgraph.response import Response 13 | from microsoftgraph.users import Users 14 | from microsoftgraph.webhooks import Webhooks 15 | from microsoftgraph.workbooks import Workbooks 16 | 17 | 18 | class Client(object): 19 | AUTHORITY_URL = "https://login.microsoftonline.com/" 20 | AUTH_ENDPOINT = "/oauth2/v2.0/authorize?" 21 | TOKEN_ENDPOINT = "/oauth2/v2.0/token" 22 | RESOURCE = "https://graph.microsoft.com/" 23 | 24 | def __init__( 25 | self, 26 | client_id: str, 27 | client_secret: str, 28 | api_version: str = "v1.0", 29 | account_type: str = "common", 30 | requests_hooks: dict = None, 31 | paginate: bool = True, 32 | ) -> None: 33 | """Instantiates library. 34 | 35 | Args: 36 | client_id (str): Application client id. 37 | client_secret (str): Application client secret. 38 | api_version (str, optional): v1.0 or beta. Defaults to "v1.0". 39 | account_type (str, optional): common, organizations or consumers. Defaults to "common". 40 | requests_hooks (dict, optional): Requests library event hooks. Defaults to None. 41 | 42 | Raises: 43 | Exception: requests_hooks is not a dict. 44 | """ 45 | self.client_id = client_id 46 | self.client_secret = client_secret 47 | self.api_version = api_version 48 | self.account_type = account_type 49 | 50 | self.base_url = self.RESOURCE + self.api_version + "/" 51 | self.token = None 52 | self.workbook_session_id = None 53 | self.paginate = paginate 54 | 55 | self.calendar = Calendar(self) 56 | self.contacts = Contacts(self) 57 | self.files = Files(self) 58 | self.mail = Mail(self) 59 | self.notes = Notes(self) 60 | self.users = Users(self) 61 | self.webhooks = Webhooks(self) 62 | self.workbooks = Workbooks(self) 63 | 64 | if requests_hooks and not isinstance(requests_hooks, dict): 65 | raise Exception( 66 | 'requests_hooks must be a dict. e.g. {"response": func}. http://docs.python-requests.org/en/master/user/advanced/#event-hooks' 67 | ) 68 | self.requests_hooks = requests_hooks 69 | 70 | def authorization_url(self, redirect_uri: str, scope: list, state: str = None) -> str: 71 | """Generates an Authorization URL. 72 | 73 | The first step to getting an access token for many OpenID Connect (OIDC) and OAuth 2.0 flows is to redirect the 74 | user to the Microsoft identity platform /authorize endpoint. Azure AD will sign the user in and ensure their 75 | consent for the permissions your app requests. In the authorization code grant flow, after consent is obtained, 76 | Azure AD will return an authorization_code to your app that it can redeem at the Microsoft identity platform 77 | /token endpoint for an access token. 78 | 79 | https://docs.microsoft.com/en-us/graph/auth-v2-user#2-get-authorization 80 | 81 | Args: 82 | redirect_uri (str): The redirect_uri of your app, where authentication responses can be sent and received by 83 | your app. It must exactly match one of the redirect_uris you registered in the app registration portal. 84 | scope (list): A list of the Microsoft Graph permissions that you want the user to consent to. This may also 85 | include OpenID scopes. 86 | state (str, optional): A value included in the request that will also be returned in the token response. 87 | It can be a string of any content that you wish. A randomly generated unique value is typically 88 | used for preventing cross-site request forgery attacks. The state is also used to encode information 89 | about the user's state in the app before the authentication request occurred, such as the page or view 90 | they were on. Defaults to None. 91 | 92 | Returns: 93 | str: Url for OAuth 2.0. 94 | """ 95 | params = { 96 | "client_id": self.client_id, 97 | "redirect_uri": redirect_uri, 98 | "scope": " ".join(scope), 99 | "response_type": "code", 100 | "response_mode": "query", 101 | } 102 | 103 | if state: 104 | params["state"] = state 105 | response = self.AUTHORITY_URL + self.account_type + self.AUTH_ENDPOINT + urlencode(params) 106 | return response 107 | 108 | def exchange_code(self, redirect_uri: str, code: str) -> Response: 109 | """Exchanges an oauth code for an user token. 110 | 111 | Your app uses the authorization code received in the previous step to request an access token by sending a POST 112 | request to the /token endpoint. 113 | 114 | https://docs.microsoft.com/en-us/graph/auth-v2-user#3-get-a-token 115 | 116 | Args: 117 | redirect_uri (str): The redirect_uri of your app, where authentication responses can be sent and received by 118 | your app. It must exactly match one of the redirect_uris you registered in the app registration portal. 119 | code (str): The authorization_code that you acquired in the first leg of the flow. 120 | 121 | Returns: 122 | Response: Microsoft Graph Response. 123 | """ 124 | data = { 125 | "client_id": self.client_id, 126 | "redirect_uri": redirect_uri, 127 | "client_secret": self.client_secret, 128 | "code": code, 129 | "grant_type": "authorization_code", 130 | } 131 | response = requests.post(self.AUTHORITY_URL + self.account_type + self.TOKEN_ENDPOINT, data=data) 132 | return self._parse(response) 133 | 134 | def refresh_token(self, redirect_uri: str, refresh_token: str) -> Response: 135 | """Exchanges a refresh token for an user token. 136 | 137 | Access tokens are short lived, and you must refresh them after they expire to continue accessing resources. 138 | You can do so by submitting another POST request to the /token endpoint, this time providing the refresh_token 139 | instead of the code. 140 | 141 | https://docs.microsoft.com/en-us/graph/auth-v2-user#5-use-the-refresh-token-to-get-a-new-access-token 142 | 143 | Args: 144 | redirect_uri (str): The redirect_uri of your app, where authentication responses can be sent and received by 145 | your app. It must exactly match one of the redirect_uris you registered in the app registration portal. 146 | refresh_token (str): An OAuth 2.0 refresh token. Your app can use this token acquire additional access tokens 147 | after the current access token expires. Refresh tokens are long-lived, and can be used to retain access 148 | to resources for extended periods of time. 149 | 150 | Returns: 151 | Response: Microsoft Graph Response. 152 | """ 153 | data = { 154 | "client_id": self.client_id, 155 | "redirect_uri": redirect_uri, 156 | "client_secret": self.client_secret, 157 | "refresh_token": refresh_token, 158 | "grant_type": "refresh_token", 159 | } 160 | response = requests.post(self.AUTHORITY_URL + self.account_type + self.TOKEN_ENDPOINT, data=data) 161 | return self._parse(response) 162 | 163 | def set_token(self, token: dict) -> None: 164 | """Sets the User token for its use in this library. 165 | 166 | Args: 167 | token (dict): User token data. 168 | """ 169 | self.token = token 170 | 171 | def set_workbook_session_id(self, workbook_session_id: dict) -> None: 172 | """Sets the Workbook Session Id token for its use in this library. 173 | 174 | Args: 175 | token (dict): Workbook Session ID. 176 | """ 177 | self.workbook_session_id = workbook_session_id 178 | 179 | def get_next(self, response: Response) -> Optional[Response]: 180 | """Retrieves the next page for the argument response if any. This allows to perform a loop in case you 181 | want to paginate the response yourself. 182 | 183 | Args: 184 | response (Response): Graph API Response. 185 | 186 | Returns: 187 | Optional[Response]: Graph API Response if available, None otherwise 188 | """ 189 | if not isinstance(response.data, dict): 190 | return None 191 | 192 | if "@odata.nextLink" not in response.data: 193 | return None 194 | 195 | return self._do_get(response.data["@odata.nextLink"]) 196 | 197 | def _paginate_response(self, response: Response) -> Response: 198 | """Some queries against Microsoft Graph return multiple pages of data either due to server-side paging or due to 199 | the use of the $top query parameter to specifically limit the page size in a request. When a result set spans 200 | multiple pages, Microsoft Graph returns an @odata.nextLink property in the response that contains a URL to the 201 | next page of results. 202 | 203 | https://docs.microsoft.com/en-us/graph/paging?context=graph%2Fapi%2F1.0&view=graph-rest-1.0 204 | 205 | Args: 206 | response (Response): Graph API Response. 207 | 208 | Returns: 209 | Response: Graph API Response. 210 | """ 211 | if not isinstance(response.data, dict) or "value" not in response.data: 212 | return response 213 | 214 | # Copy data to avoid side effects 215 | data = list(response.data["value"]) 216 | 217 | while "@odata.nextLink" in response.data: 218 | response = self.get_next(response) 219 | if isinstance(response.data, dict) and "value" in response.data: 220 | data.extend(response.data["value"]) 221 | 222 | response.data["value"] = data 223 | return response 224 | 225 | def _get(self, url, **kwargs) -> Response: 226 | response = self._do_get(url, **kwargs) 227 | if self.paginate: 228 | return self._paginate_response(response) 229 | 230 | return response 231 | 232 | def _do_get(self, url, **kwargs) -> Response: 233 | return self._request("GET", url, **kwargs) 234 | 235 | def _post(self, url, **kwargs): 236 | return self._request("POST", url, **kwargs) 237 | 238 | def _put(self, url, **kwargs): 239 | return self._request("PUT", url, **kwargs) 240 | 241 | def _patch(self, url, **kwargs): 242 | return self._request("PATCH", url, **kwargs) 243 | 244 | def _delete(self, url, **kwargs): 245 | return self._request("DELETE", url, **kwargs) 246 | 247 | def _request(self, method, url, headers=None, **kwargs) -> Response: 248 | _headers = { 249 | "Accept": "application/json", 250 | } 251 | 252 | if headers: 253 | _headers.update(headers) 254 | 255 | _headers["Authorization"] = "Bearer " + self.token["access_token"] 256 | 257 | if self.requests_hooks: 258 | kwargs.update({"hooks": self.requests_hooks}) 259 | if "Content-Type" not in _headers: 260 | _headers["Content-Type"] = "application/json" 261 | return self._parse(requests.request(method, url, headers=_headers, **kwargs)) 262 | 263 | def _parse(self, response) -> Response: 264 | status_code = response.status_code 265 | r = Response(original=response) 266 | if status_code < 299: 267 | return r 268 | elif status_code == 400: 269 | raise exceptions.BadRequest(r.data) 270 | elif status_code == 401: 271 | raise exceptions.Unauthorized(r.data) 272 | elif status_code == 403: 273 | raise exceptions.Forbidden(r.data) 274 | elif status_code == 404: 275 | raise exceptions.NotFound(r.data) 276 | elif status_code == 405: 277 | raise exceptions.MethodNotAllowed(r.data) 278 | elif status_code == 406: 279 | raise exceptions.NotAcceptable(r.data) 280 | elif status_code == 409: 281 | raise exceptions.Conflict(r.data) 282 | elif status_code == 410: 283 | raise exceptions.Gone(r.data) 284 | elif status_code == 411: 285 | raise exceptions.LengthRequired(r.data) 286 | elif status_code == 412: 287 | raise exceptions.PreconditionFailed(r.data) 288 | elif status_code == 413: 289 | raise exceptions.RequestEntityTooLarge(r.data) 290 | elif status_code == 415: 291 | raise exceptions.UnsupportedMediaType(r.data) 292 | elif status_code == 416: 293 | raise exceptions.RequestedRangeNotSatisfiable(r.data) 294 | elif status_code == 422: 295 | raise exceptions.UnprocessableEntity(r.data) 296 | elif status_code == 429: 297 | raise exceptions.TooManyRequests(r.data) 298 | elif status_code == 500: 299 | raise exceptions.InternalServerError(r.data) 300 | elif status_code == 501: 301 | raise exceptions.NotImplemented(r.data) 302 | elif status_code == 503: 303 | raise exceptions.ServiceUnavailable(r.data) 304 | elif status_code == 504: 305 | raise exceptions.GatewayTimeout(r.data) 306 | elif status_code == 507: 307 | raise exceptions.InsufficientStorage(r.data) 308 | elif status_code == 509: 309 | raise exceptions.BandwidthLimitExceeded(r.data) 310 | else: 311 | if r["error"]["innerError"]["code"] == "lockMismatch": 312 | # File is currently locked due to being open in the web browser 313 | # while attempting to reupload a new version to the drive. 314 | # Thus temporarily unavailable. 315 | raise exceptions.ServiceUnavailable(r.data) 316 | raise exceptions.UnknownError(r.data) 317 | -------------------------------------------------------------------------------- /microsoftgraph/contacts.py: -------------------------------------------------------------------------------- 1 | from microsoftgraph.decorators import token_required 2 | from microsoftgraph.response import Response 3 | 4 | 5 | class Contacts(object): 6 | def __init__(self, client) -> None: 7 | """Working with Outlook Contacts. 8 | 9 | https://docs.microsoft.com/en-us/graph/api/resources/contact?view=graph-rest-1.0 10 | 11 | Args: 12 | client (Client): Library Client. 13 | """ 14 | self._client = client 15 | 16 | @token_required 17 | def get_contact(self, contact_id: str, params: dict = None) -> Response: 18 | """Retrieve the properties and relationships of a contact object. 19 | 20 | https://docs.microsoft.com/en-us/graph/api/contact-get?view=graph-rest-1.0&tabs=http 21 | 22 | Args: 23 | contact_id (str): The contact's unique identifier. 24 | params (dict, optional): Query. Defaults to None. 25 | 26 | Returns: 27 | Response: Microsoft Graph Response. 28 | """ 29 | return self._client._get(self._client.base_url + "me/contacts/{}".format(contact_id), params=params) 30 | 31 | @token_required 32 | def list_contacts(self, folder_id: str = None, params: dict = None) -> Response: 33 | """Get a contact collection from the default contacts folder of the signed-in user. 34 | 35 | https://docs.microsoft.com/en-us/graph/api/user-list-contacts?view=graph-rest-1.0&tabs=http 36 | 37 | Args: 38 | folder_id (str): Folder ID. 39 | params (dict, optional): Query. Defaults to None. 40 | 41 | Returns: 42 | Response: Microsoft Graph Response. 43 | """ 44 | url = "me/contactfolders/{}/contacts".format(folder_id) if folder_id else "me/contacts" 45 | return self._client._get(self._client.base_url + url, params=params) 46 | 47 | @token_required 48 | def create_contact( 49 | self, 50 | given_name: str, 51 | surname: str, 52 | email_addresses: list, 53 | business_phones: list, 54 | folder_id: str = None, 55 | **kwargs 56 | ) -> Response: 57 | """Add a contact to the root Contacts folder or to the contacts endpoint of another contact folder. 58 | 59 | https://docs.microsoft.com/en-us/graph/api/user-post-contacts?view=graph-rest-1.0&tabs=http 60 | 61 | Args: 62 | given_name (str): The contact's given name. 63 | surname (str): The contact's surname. 64 | email_addresses (list): The contact's email addresses. 65 | business_phones (list): The contact's business phone numbers. 66 | folder_id (str, optional): Unique identifier of the contact folder. Defaults to None. 67 | 68 | Returns: 69 | Response: Microsoft Graph Response. 70 | """ 71 | if isinstance(email_addresses, str): 72 | email_addresses = [{"address": email_addresses, "name": "{} {}".format(given_name, surname)}] 73 | 74 | if isinstance(business_phones, str): 75 | business_phones = [business_phones] 76 | 77 | body = { 78 | "givenName": given_name, 79 | "surname": surname, 80 | "emailAddresses": email_addresses, 81 | "businessPhones": business_phones, 82 | } 83 | body.update(kwargs) 84 | url = "me/contactFolders/{}/contacts".format(folder_id) if folder_id else "me/contacts" 85 | return self._client._post(self._client.base_url + url, json=body) 86 | 87 | @token_required 88 | def list_contact_folders(self, params: dict = None) -> Response: 89 | """Get the contact folder collection in the default Contacts folder of the signed-in user. 90 | 91 | https://docs.microsoft.com/en-us/graph/api/user-list-contactfolders?view=graph-rest-1.0&tabs=http 92 | 93 | Args: 94 | params (dict, optional): Query. Defaults to None. 95 | 96 | Returns: 97 | Response: Microsoft Graph Response. 98 | """ 99 | return self._client._get(self._client.base_url + "me/contactFolders", params=params) 100 | 101 | @token_required 102 | def create_contact_folder(self, display_name: str, parent_folder_id: str, **kwargs) -> Response: 103 | """Create a new contactFolder under the user's default contacts folder. 104 | 105 | https://docs.microsoft.com/en-us/graph/api/user-post-contactfolders?view=graph-rest-1.0&tabs=http 106 | 107 | Args: 108 | display_name (str): The folder's display name. 109 | parent_folder_id (str): The ID of the folder's parent folder. 110 | 111 | Returns: 112 | Response: Microsoft Graph Response. 113 | """ 114 | data = {"displayName": display_name, "parentFolderId": parent_folder_id} 115 | data.update(kwargs) 116 | return self._client._post(self._client.base_url + "me/contactFolders", json=data) 117 | -------------------------------------------------------------------------------- /microsoftgraph/decorators.py: -------------------------------------------------------------------------------- 1 | from microsoftgraph.exceptions import TokenRequired 2 | from functools import wraps 3 | 4 | 5 | def token_required(func): 6 | @wraps(func) 7 | def helper(*args, **kwargs): 8 | module = args[0] 9 | if not module._client.token: 10 | raise TokenRequired("You must set the Token.") 11 | return func(*args, **kwargs) 12 | 13 | return helper 14 | 15 | 16 | def workbook_session_id_required(func): 17 | @wraps(func) 18 | def helper(*args, **kwargs): 19 | module = args[0] 20 | if not module._client.workbook_session_id: 21 | raise TokenRequired("You must set the Workbook Session Id.") 22 | return func(*args, **kwargs) 23 | 24 | return helper 25 | -------------------------------------------------------------------------------- /microsoftgraph/exceptions.py: -------------------------------------------------------------------------------- 1 | class BaseError(Exception): 2 | pass 3 | 4 | 5 | class UnknownError(BaseError): 6 | pass 7 | 8 | 9 | class TokenRequired(BaseError): 10 | pass 11 | 12 | 13 | class BadRequest(BaseError): 14 | pass 15 | 16 | 17 | class Unauthorized(BaseError): 18 | pass 19 | 20 | 21 | class Forbidden(BaseError): 22 | pass 23 | 24 | 25 | class NotFound(BaseError): 26 | pass 27 | 28 | 29 | class MethodNotAllowed(BaseError): 30 | pass 31 | 32 | 33 | class NotAcceptable(BaseError): 34 | pass 35 | 36 | 37 | class Conflict(BaseError): 38 | pass 39 | 40 | 41 | class Gone(BaseError): 42 | pass 43 | 44 | 45 | class LengthRequired(BaseError): 46 | pass 47 | 48 | 49 | class PreconditionFailed(BaseError): 50 | pass 51 | 52 | 53 | class RequestEntityTooLarge(BaseError): 54 | pass 55 | 56 | 57 | class UnsupportedMediaType(BaseError): 58 | pass 59 | 60 | 61 | class RequestedRangeNotSatisfiable(BaseError): 62 | pass 63 | 64 | 65 | class UnprocessableEntity(BaseError): 66 | pass 67 | 68 | 69 | class TooManyRequests(BaseError): 70 | pass 71 | 72 | 73 | class InternalServerError(BaseError): 74 | pass 75 | 76 | 77 | class NotImplemented(BaseError): 78 | pass 79 | 80 | 81 | class ServiceUnavailable(BaseError): 82 | pass 83 | 84 | 85 | class GatewayTimeout(BaseError): 86 | pass 87 | 88 | 89 | class InsufficientStorage(BaseError): 90 | pass 91 | 92 | 93 | class BandwidthLimitExceeded(BaseError): 94 | pass 95 | -------------------------------------------------------------------------------- /microsoftgraph/files.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | import requests 4 | 5 | from microsoftgraph.decorators import token_required 6 | from microsoftgraph.response import Response 7 | 8 | 9 | class Files(object): 10 | def __init__(self, client) -> None: 11 | """Working with files in Microsoft Graph 12 | 13 | https://docs.microsoft.com/en-us/graph/api/resources/onedrive?view=graph-rest-1.0 14 | 15 | Args: 16 | client (Client): Library Client. 17 | """ 18 | self._client = client 19 | 20 | @token_required 21 | def drive_root_items(self, params: dict = None) -> Response: 22 | """Return a collection of DriveItems in the children relationship of a DriveItem. 23 | 24 | https://docs.microsoft.com/en-us/graph/api/driveitem-list-children?view=graph-rest-1.0&tabs=http 25 | 26 | Args: 27 | params (dict, optional): Query. Defaults to None. 28 | 29 | Returns: 30 | Response: Microsoft Graph Response. 31 | """ 32 | return self._client._get(self._client.base_url + "me/drive/root", params=params) 33 | 34 | @token_required 35 | def drive_root_children_items(self, params: dict = None) -> Response: 36 | """Return a collection of DriveItems in the children relationship of a DriveItem. 37 | 38 | https://docs.microsoft.com/en-us/graph/api/driveitem-list-children?view=graph-rest-1.0&tabs=http 39 | 40 | Args: 41 | params (dict, optional): Query. Defaults to None. 42 | 43 | Returns: 44 | Response: Microsoft Graph Response. 45 | """ 46 | return self._client._get(self._client.base_url + "me/drive/root/children", params=params) 47 | 48 | @token_required 49 | def drive_specific_folder(self, folder_id: str, params: dict = None) -> Response: 50 | """Return a collection of DriveItems in the children relationship of a DriveItem. 51 | 52 | https://docs.microsoft.com/en-us/graph/api/driveitem-list-children?view=graph-rest-1.0&tabs=http 53 | 54 | Args: 55 | folder_id (str): Unique identifier of the folder. 56 | params (dict, optional): Query. Defaults to None. 57 | 58 | Returns: 59 | Response: Microsoft Graph Response. 60 | """ 61 | url = "me/drive/items/{}/children".format(folder_id) 62 | return self._client._get(self._client.base_url + url, params=params) 63 | 64 | @token_required 65 | def drive_get_item(self, item_id: str, params: dict = None, **kwargs) -> Response: 66 | """Retrieve the metadata for a driveItem in a drive by file system path or ID. It may also be the unique ID of a 67 | SharePoint list item. 68 | 69 | https://docs.microsoft.com/en-us/graph/api/driveitem-get?view=graph-rest-1.0&tabs=http 70 | 71 | Args: 72 | item_id (str): ID of a driveItem. 73 | params (dict, optional): Query. Defaults to None. 74 | 75 | Returns: 76 | Response: Microsoft Graph Response. 77 | """ 78 | url = "me/drive/items/{}".format(item_id) 79 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 80 | 81 | @token_required 82 | def drive_download_contents(self, item_id: str, params: dict = None, **kwargs) -> Response: 83 | """Download the contents of the primary stream (file) of a DriveItem. Only driveItems with the file property can 84 | be downloaded. 85 | 86 | https://docs.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0&tabs=http 87 | 88 | Args: 89 | item_id (str): ID of a driveItem. 90 | params (dict, optional): Extra params. Defaults to None. 91 | 92 | Returns: 93 | Response: Microsoft Graph Response. 94 | """ 95 | url = "me/drive/items/{}/content".format(item_id) 96 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 97 | 98 | @token_required 99 | def drive_download_shared_contents(self, share_id: str, params: dict = None, **kwargs) -> Response: 100 | """Download the contents of the primary stream (file) of a DriveItem. Only driveItems with the file property can 101 | be downloaded. 102 | 103 | https://docs.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0&tabs=http 104 | 105 | Args: 106 | share_id (str): ID of a driveItem. 107 | params (dict, optional): Extra params. Defaults to None. 108 | 109 | Returns: 110 | Response: Microsoft Graph Response. 111 | """ 112 | base64_value = base64.b64encode(share_id.encode()).decode() 113 | encoded_share_url = "u!" + base64_value.rstrip("=").replace("/", "_").replace("+", "-") 114 | url = self._client.base_url + "shares/{}/driveItem".format(encoded_share_url) 115 | drive_item = self._client._get(url) 116 | file_download_url = drive_item["@microsoft.graph.downloadUrl"] 117 | return drive_item["name"], requests.get(file_download_url).content 118 | 119 | @token_required 120 | def drive_download_large_contents(self, downloadUrl: str, offset: int, size: int) -> Response: 121 | """Download the contents of the primary stream (file) of a DriveItem. Only driveItems with the file property can 122 | be downloaded. 123 | 124 | https://docs.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0&tabs=http 125 | 126 | Args: 127 | downloadUrl (str): Url of the driveItem. 128 | offset (int): offset. 129 | size (int): size. 130 | 131 | Returns: 132 | Response: Microsoft Graph Response. 133 | """ 134 | headers = {"Range": f"bytes={offset}-{size + offset - 1}"} 135 | return self._client._get(downloadUrl, headers=headers) 136 | 137 | @token_required 138 | def drive_upload_new_file(self, filename: str, file_path: str, params: dict = None, **kwargs) -> Response: 139 | """The simple upload API allows you to provide the contents of a new file in a single API call. This method only 140 | supports files up to 4MB in size. 141 | 142 | https://docs.microsoft.com/en-us/graph/api/driveitem-put-content?view=graph-rest-1.0&tabs=http 143 | 144 | Args: 145 | filename (str): The name of the item (filename and extension). 146 | file_path (str): File path to upload. 147 | params (dict, optional): Extra params. Defaults to None. 148 | 149 | Returns: 150 | Response: Microsoft Graph Response. 151 | """ 152 | kwargs["headers"] = {"Content-Type": "text/plain"} 153 | content = open(file_path, "rb").read() 154 | url = "me/drive/root:{}:/content".format(filename) 155 | return self._client._put(self._client.base_url + url, params=params, data=content, **kwargs) 156 | 157 | @token_required 158 | def drive_update_existing_file(self, item_id: str, file_path: str, params: dict = None, **kwargs) -> Response: 159 | """The simple upload API allows you to update the contents of an existing file in a single API call. This method 160 | only supports files up to 4MB in size. 161 | 162 | https://docs.microsoft.com/en-us/graph/api/driveitem-put-content?view=graph-rest-1.0&tabs=http 163 | 164 | Args: 165 | item_id (str): Id of a driveItem. 166 | file_path (str): File path to upload. 167 | params (dict, optional): Extra params. Defaults to None. 168 | 169 | Returns: 170 | Response: Microsoft Graph Response. 171 | """ 172 | kwargs["headers"] = {"Content-Type": "text/plain"} 173 | content = open(file_path, "rb").read() 174 | url = "me/drive/items/{}/content".format(item_id) 175 | return self._client._put(self._client.base_url + url, params=params, data=content, **kwargs) 176 | 177 | @token_required 178 | def search_items(self, q: str, params: dict = None, **kwargs) -> Response: 179 | """Search the hierarchy of items for items matching a query. You can search within a folder hierarchy, a whole 180 | drive, or files shared with the current user. 181 | 182 | https://docs.microsoft.com/en-us/graph/api/driveitem-search?view=graph-rest-1.0&tabs=http 183 | 184 | Args: 185 | q (str): The query text used to search for items. Values may be matched across several fields including 186 | filename, metadata, and file content. 187 | params (dict, optional): Query. Defaults to None. 188 | 189 | Returns: 190 | Response: Microsoft Graph Response. 191 | """ 192 | url = "me/drive/root/search(q='{}')".format(q) 193 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 194 | -------------------------------------------------------------------------------- /microsoftgraph/mail.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import mimetypes 3 | 4 | from microsoftgraph.decorators import token_required 5 | from microsoftgraph.response import Response 6 | 7 | 8 | class Mail(object): 9 | def __init__(self, client) -> None: 10 | """Use the Outlook mail REST API 11 | 12 | https://docs.microsoft.com/en-us/graph/api/resources/mail-api-overview?view=graph-rest-1.0 13 | 14 | Args: 15 | client (Client): Library Client. 16 | """ 17 | self._client = client 18 | 19 | @token_required 20 | def list_messages(self, folder_id: str = None, params: dict = None) -> Response: 21 | """Get the messages in the signed-in user's mailbox (including the Deleted Items and Clutter folders). 22 | 23 | https://docs.microsoft.com/en-us/graph/api/user-list-messages?view=graph-rest-1.0&tabs=http 24 | 25 | Args: 26 | folder_id (str, optional): Mail Folder ID. 27 | params (dict, optional): Query. Defaults to None. 28 | 29 | Returns: 30 | Response: Microsoft Graph Response. 31 | """ 32 | url = "me/mailFolders/{}/messages".format(folder_id) if folder_id else "me/messages" 33 | return self._client._get(self._client.base_url + url, params=params) 34 | 35 | @token_required 36 | def get_message(self, message_id: str, params: dict = None) -> Response: 37 | """Retrieve the properties and relationships of a message object. 38 | 39 | https://docs.microsoft.com/en-us/graph/api/message-get?view=graph-rest-1.0&tabs=http 40 | 41 | Args: 42 | message_id (str): Unique identifier for the message. 43 | params (dict, optional): Query. Defaults to None. 44 | 45 | Returns: 46 | Response: Microsoft Graph Response. 47 | """ 48 | return self._client._get(self._client.base_url + "me/messages/" + message_id, params=params) 49 | 50 | @token_required 51 | def send_mail( 52 | self, 53 | subject: str, 54 | content: str, 55 | to_recipients: list, 56 | cc_recipients: list = None, 57 | content_type: str = "HTML", 58 | attachments: list = None, 59 | save_to_sent_items: bool = True, 60 | **kwargs, 61 | ) -> Response: 62 | """Send the message specified in the request body using either JSON or MIME format. 63 | 64 | https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=http 65 | 66 | Args: 67 | subject (str): The subject of the message. 68 | content (str): The body of the message. 69 | to_recipients (list, optional): The To: recipients for the message. 70 | cc_recipients (list, optional): The Cc: recipients for the message. Defaults to None. 71 | content_type (str, optional): It can be in HTML or text format. Defaults to "HTML". 72 | attachments (list, optional): The fileAttachment and itemAttachment attachments for the message. Defaults to None. 73 | save_to_sent_items (bool, optional): Indicates whether to save the message in Sent Items. Defaults to True. 74 | 75 | Returns: 76 | Response: Microsoft Graph Response. 77 | """ 78 | # Create recipient list in required format. 79 | if isinstance(to_recipients, list): 80 | if all([isinstance(e, str) for e in to_recipients]): 81 | to_recipients = [{"EmailAddress": {"Address": address}} for address in to_recipients] 82 | elif isinstance(to_recipients, str): 83 | to_recipients = [{"EmailAddress": {"Address": to_recipients}}] 84 | else: 85 | raise Exception("to_recipients value is invalid.") 86 | 87 | if cc_recipients and isinstance(cc_recipients, list): 88 | if all([isinstance(e, str) for e in cc_recipients]): 89 | cc_recipients = [{"EmailAddress": {"Address": address}} for address in cc_recipients] 90 | elif cc_recipients and isinstance(cc_recipients, str): 91 | cc_recipients = [{"EmailAddress": {"Address": cc_recipients}}] 92 | else: 93 | cc_recipients = [] 94 | 95 | # Create list of attachments in required format. 96 | attached_files = [] 97 | if attachments: 98 | for filename in attachments: 99 | b64_content = base64.b64encode(open(filename, "rb").read()) 100 | mime_type = mimetypes.guess_type(filename)[0] 101 | mime_type = mime_type if mime_type else "" 102 | attached_files.append( 103 | { 104 | "@odata.type": "#microsoft.graph.fileAttachment", 105 | "ContentBytes": b64_content.decode("utf-8"), 106 | "ContentType": mime_type, 107 | "Name": filename, 108 | } 109 | ) 110 | 111 | # Create email message in required format. 112 | email_msg = { 113 | "Message": { 114 | "Subject": subject, 115 | "Body": {"ContentType": content_type, "Content": content}, 116 | "ToRecipients": to_recipients, 117 | "ccRecipients": cc_recipients, 118 | "Attachments": attached_files, 119 | }, 120 | "SaveToSentItems": save_to_sent_items, 121 | } 122 | email_msg.update(kwargs) 123 | 124 | # Do a POST to Graph's sendMail API and return the response. 125 | return self._client._post(self._client.base_url + "me/sendMail", json=email_msg) 126 | 127 | @token_required 128 | def list_mail_folders(self, params: dict = None) -> Response: 129 | """Get the mail folder collection directly under the root folder of the signed-in user. The returned collection 130 | includes any mail search folders directly under the root. 131 | 132 | By default, this operation does not return hidden folders. Use a query parameter includeHiddenFolders to include 133 | them in the response. 134 | 135 | https://docs.microsoft.com/en-us/graph/api/user-list-mailfolders?view=graph-rest-1.0&tabs=http 136 | 137 | Args: 138 | params (dict, optional): Query. Defaults to None. 139 | 140 | Returns: 141 | Response: Microsoft Graph Response. 142 | """ 143 | return self._client._get(self._client.base_url + "me/mailFolders", params=params) 144 | 145 | @token_required 146 | def create_mail_folder(self, display_name: str, is_hidden: bool = False) -> Response: 147 | """Use this API to create a new mail folder in the root folder of the user's mailbox. 148 | 149 | https://docs.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http 150 | 151 | Args: 152 | display_name (str): Query. 153 | is_hidden (bool, optional): Is the folder hidden. Defaults to False. 154 | 155 | Returns: 156 | Response: Microsoft Graph Response. 157 | """ 158 | data = { 159 | "displayName": display_name, 160 | "isHidden": is_hidden, 161 | } 162 | return self._client._post(self._client.base_url + "me/mailFolders", json=data) 163 | -------------------------------------------------------------------------------- /microsoftgraph/notes.py: -------------------------------------------------------------------------------- 1 | from microsoftgraph.decorators import token_required 2 | from microsoftgraph.response import Response 3 | 4 | 5 | class Notes(object): 6 | def __init__(self, client) -> None: 7 | """Use the OneNote REST API. 8 | 9 | https://docs.microsoft.com/en-us/graph/api/resources/onenote-api-overview?view=graph-rest-1.0 10 | 11 | Args: 12 | client (Client): Library Client. 13 | """ 14 | self._client = client 15 | 16 | @token_required 17 | def list_notebooks(self, params: dict = None) -> Response: 18 | """Retrieve a list of notebook objects. 19 | 20 | https://docs.microsoft.com/en-us/graph/api/onenote-list-notebooks?view=graph-rest-1.0&tabs=http 21 | 22 | Args: 23 | params (dict, optional): Query. Defaults to None. 24 | 25 | Returns: 26 | Response: Microsoft Graph Response. 27 | """ 28 | return self._client._get(self._client.base_url + "me/onenote/notebooks", params=params) 29 | 30 | @token_required 31 | def get_notebook(self, notebook_id: str, params: dict = None) -> Response: 32 | """Retrieve the properties and relationships of a notebook object. 33 | 34 | https://docs.microsoft.com/en-us/graph/api/notebook-get?view=graph-rest-1.0&tabs=http 35 | 36 | Args: 37 | notebook_id (str): The unique identifier of the notebook. 38 | params (dict, optional): Query. Defaults to None. 39 | 40 | Returns: 41 | Response: Microsoft Graph Response. 42 | """ 43 | return self._client._get(self._client.base_url + "me/onenote/notebooks/" + notebook_id, params=params) 44 | 45 | @token_required 46 | def list_sections(self, notebook_id: str, params: dict = None) -> Response: 47 | """Retrieve a list of onenoteSection objects from the specified notebook. 48 | 49 | https://docs.microsoft.com/en-us/graph/api/notebook-list-sections?view=graph-rest-1.0&tabs=http 50 | 51 | Args: 52 | notebook_id (str): The unique identifier of the notebook. 53 | params (dict, optional): Query. Defaults to None. 54 | 55 | Returns: 56 | Response: Microsoft Graph Response. 57 | """ 58 | url = "me/onenote/notebooks/{}/sections".format(notebook_id) 59 | return self._client._get(self._client.base_url + url, params=params) 60 | 61 | @token_required 62 | def list_pages(self, params: dict = None) -> Response: 63 | """Retrieve a list of page objects. 64 | 65 | Args: 66 | params (dict, optional): Query. Defaults to None. 67 | 68 | Returns: 69 | Response: Microsoft Graph Response. 70 | """ 71 | return self._client._get(self._client.base_url + "me/onenote/pages", params=params) 72 | 73 | @token_required 74 | def create_page(self, section_id: str, files: list) -> Response: 75 | """Create a new page in the specified section. 76 | 77 | https://docs.microsoft.com/en-us/graph/api/section-post-pages?view=graph-rest-1.0 78 | 79 | Args: 80 | section_id (str): The unique identifier of the section. 81 | files (list): Attachments. 82 | 83 | Returns: 84 | Response: Microsoft Graph Response. 85 | """ 86 | url = "me/onenote/sections/{}/pages".format(section_id) 87 | return self._client._post(self._client.base_url + url, files=files) 88 | -------------------------------------------------------------------------------- /microsoftgraph/response.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | 4 | class Response: 5 | def __init__(self, original) -> None: 6 | self.original = original 7 | 8 | if "application/json" in self.original.headers.get("Content-Type", ""): 9 | self.data = self.original.json() 10 | else: 11 | self.data = self.original.content 12 | 13 | def __repr__(self) -> str: 14 | return "".format(self.status_code) 15 | 16 | @property 17 | def status_code(self): 18 | return self.original.status_code 19 | 20 | @property 21 | def throttling(self) -> datetime: 22 | """Microsoft Graph throttling 23 | 24 | https://docs.microsoft.com/en-us/graph/throttling 25 | 26 | Returns: 27 | datetime: Retry after. 28 | """ 29 | if "Retry-After" in self.original.headers: 30 | return datetime.now() + timedelta(seconds=self.original.headers["Retry-After"]) 31 | return None 32 | -------------------------------------------------------------------------------- /microsoftgraph/users.py: -------------------------------------------------------------------------------- 1 | from microsoftgraph.decorators import token_required 2 | from microsoftgraph.response import Response 3 | 4 | 5 | class Users(object): 6 | def __init__(self, client) -> None: 7 | """Working with users in Microsoft Graph. 8 | 9 | https://docs.microsoft.com/en-us/graph/api/resources/users?view=graph-rest-1.0 10 | 11 | Args: 12 | client (Client): Library Client. 13 | """ 14 | self._client = client 15 | 16 | @token_required 17 | def get_me(self, params: dict = None) -> Response: 18 | """Retrieve the properties and relationships of user object. 19 | 20 | Note: Getting a user returns a default set of properties only (businessPhones, displayName, givenName, id, 21 | jobTitle, mail, mobilePhone, officeLocation, preferredLanguage, surname, userPrincipalName). 22 | Use $select to get the other properties and relationships for the user object. 23 | 24 | https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http 25 | 26 | Args: 27 | params (dict, optional): Query. Defaults to None. 28 | 29 | Returns: 30 | Response: Microsoft Graph Response. 31 | """ 32 | return self._client._get(self._client.base_url + "me", params=params) 33 | -------------------------------------------------------------------------------- /microsoftgraph/utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | 4 | def format_time(value: datetime, is_webhook: bool = False) -> str: 5 | if is_webhook: 6 | return value.strftime("%Y-%m-%dT%H:%M:%S.%fZ") 7 | return value.strftime("%Y-%m-%dT%H:%M:%S") 8 | -------------------------------------------------------------------------------- /microsoftgraph/webhooks.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from microsoftgraph.decorators import token_required 4 | from microsoftgraph.response import Response 5 | from microsoftgraph.utils import format_time 6 | 7 | 8 | class Webhooks(object): 9 | def __init__(self, client) -> None: 10 | """Set up notifications for changes in user data. 11 | 12 | https://docs.microsoft.com/en-us/graph/webhooks?view=graph-rest-1.0 13 | 14 | Args: 15 | client (Client): Library Client. 16 | """ 17 | self._client = client 18 | 19 | @token_required 20 | def create_subscription( 21 | self, 22 | change_type: str, 23 | notification_url: str, 24 | resource: str, 25 | expiration_datetime: datetime, 26 | client_state: str = None, 27 | **kwargs, 28 | ) -> Response: 29 | """Creates a subscription to start receiving notifications for a resource. 30 | 31 | https://docs.microsoft.com/en-us/graph/webhooks#creating-a-subscription 32 | 33 | Args: 34 | change_type (str): The event type that caused the notification. For example, created on mail receive, or 35 | updated on marking a message read. 36 | notification_url (str): Url to receive notifications. 37 | resource (str): The URI of the resource relative to https://graph.microsoft.com. 38 | expiration_datetime (datetime): The expiration time for the subscription. 39 | client_state (str, optional): The clientState property specified in the subscription request. Defaults to None. 40 | 41 | Returns: 42 | Response: Microsoft Graph Response. 43 | """ 44 | if isinstance(expiration_datetime, datetime): 45 | expiration_datetime = format_time(expiration_datetime, is_webhook=True) 46 | 47 | data = { 48 | "changeType": change_type, 49 | "notificationUrl": notification_url, 50 | "resource": resource, 51 | "expirationDateTime": expiration_datetime, 52 | "clientState": client_state, 53 | } 54 | data.update(kwargs) 55 | return self._client._post(self._client.base_url + "subscriptions", json=data) 56 | 57 | @token_required 58 | def renew_subscription(self, subscription_id: str, expiration_datetime: datetime) -> Response: 59 | """Renews a subscription to keep receiving notifications for a resource. 60 | 61 | https://docs.microsoft.com/en-us/graph/webhooks#renewing-a-subscription 62 | 63 | Args: 64 | subscription_id (str): Subscription ID. 65 | expiration_datetime (datetime): Expiration date. 66 | 67 | Returns: 68 | Response: Microsoft Graph Response. 69 | """ 70 | if isinstance(expiration_datetime, datetime): 71 | expiration_datetime = format_time(expiration_datetime, is_webhook=True) 72 | 73 | data = {"expirationDateTime": expiration_datetime} 74 | return self._client._patch(self._client.base_url + "subscriptions/{}".format(subscription_id), json=data) 75 | 76 | @token_required 77 | def delete_subscription(self, subscription_id: str) -> Response: 78 | """Deletes a subscription to stop receiving notifications for a resource. 79 | 80 | https://docs.microsoft.com/en-us/graph/webhooks#deleting-a-subscription 81 | 82 | Args: 83 | subscription_id (str): Subscription ID. 84 | 85 | Returns: 86 | Response: Microsoft Graph Response. 87 | """ 88 | return self._client._delete(self._client.base_url + "subscriptions/{}".format(subscription_id)) 89 | -------------------------------------------------------------------------------- /microsoftgraph/workbooks.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import quote_plus 2 | 3 | from microsoftgraph.decorators import token_required, workbook_session_id_required 4 | from microsoftgraph.response import Response 5 | 6 | 7 | class Workbooks(object): 8 | def __init__(self, client) -> None: 9 | """Working with Excel in Microsoft Graph 10 | 11 | https://docs.microsoft.com/en-us/graph/api/resources/excel?view=graph-rest-1.0 12 | 13 | Args: 14 | client (Client): Library Client. 15 | """ 16 | self._client = client 17 | 18 | @token_required 19 | def create_session(self, workbook_id: str, **kwargs) -> Response: 20 | """Create a new workbook session. 21 | 22 | https://docs.microsoft.com/en-us/graph/api/workbook-createsession?view=graph-rest-1.0&tabs=http 23 | 24 | Args: 25 | workbook_id (str): Excel file ID. 26 | 27 | Returns: 28 | Response: Microsoft Graph Response. 29 | """ 30 | url = "me/drive/items/{}/workbook/createSession".format(workbook_id) 31 | return self._client._post(self._client.base_url + url, **kwargs) 32 | 33 | @token_required 34 | @workbook_session_id_required 35 | def refresh_session(self, workbook_id: str, **kwargs) -> Response: 36 | """Refresh an existing workbook session. 37 | 38 | https://docs.microsoft.com/en-us/graph/api/workbook-refreshsession?view=graph-rest-1.0&tabs=http 39 | 40 | Args: 41 | workbook_id (str): Excel file ID. 42 | 43 | Returns: 44 | Response: Microsoft Graph Response. 45 | """ 46 | headers = {"workbook-session-id": self._client.workbook_session_id} 47 | url = "me/drive/items/{}/workbook/refreshSession".format(workbook_id) 48 | return self._client._post(self._client.base_url + url, headers=headers, **kwargs) 49 | 50 | @token_required 51 | @workbook_session_id_required 52 | def close_session(self, workbook_id: str, **kwargs) -> Response: 53 | """Close an existing workbook session. 54 | 55 | https://docs.microsoft.com/en-us/graph/api/workbook-closesession?view=graph-rest-1.0&tabs=http 56 | 57 | Args: 58 | workbook_id (str): Excel file ID. 59 | 60 | Returns: 61 | Response: Microsoft Graph Response. 62 | """ 63 | headers = {"workbook-session-id": self._client.workbook_session_id} 64 | url = "me/drive/items/{}/workbook/closeSession".format(workbook_id) 65 | return self._client._post(self._client.base_url + url, headers=headers, **kwargs) 66 | 67 | @token_required 68 | def list_names(self, workbook_id: str, params: dict = None, **kwargs) -> Response: 69 | """Retrieve a list of nameditem objects. 70 | 71 | https://docs.microsoft.com/en-us/graph/api/workbook-list-names?view=graph-rest-1.0&tabs=http 72 | 73 | Args: 74 | workbook_id (str): Excel file ID. 75 | params (dict, optional): Query. Defaults to None. 76 | 77 | Returns: 78 | Response: Microsoft Graph Response. 79 | """ 80 | url = "me/drive/items/{}/workbook/names".format(workbook_id) 81 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 82 | 83 | @token_required 84 | def list_worksheets(self, workbook_id: str, params: dict = None, **kwargs) -> Response: 85 | """Retrieve a list of worksheet objects. 86 | 87 | https://docs.microsoft.com/en-us/graph/api/workbook-list-worksheets?view=graph-rest-1.0&tabs=http 88 | 89 | Args: 90 | workbook_id (str): Excel file ID. 91 | params (dict, optional): Query. Defaults to None. 92 | 93 | Returns: 94 | Response: Microsoft Graph Response. 95 | """ 96 | url = "me/drive/items/{}/workbook/worksheets".format(workbook_id) 97 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 98 | 99 | @token_required 100 | def get_worksheet(self, workbook_id: str, worksheet_id: str, **kwargs) -> Response: 101 | """Retrieve the properties and relationships of worksheet object. 102 | 103 | https://docs.microsoft.com/en-us/graph/api/worksheet-get?view=graph-rest-1.0&tabs=http 104 | 105 | Args: 106 | workbook_id (str): Excel file ID. 107 | worksheet_id (str): Excel worksheet ID. 108 | 109 | Returns: 110 | Response: Microsoft Graph Response. 111 | """ 112 | url = "me/drive/items/{}/workbook/worksheets/{}".format(workbook_id, quote_plus(worksheet_id)) 113 | return self._client._get(self._client.base_url + url, **kwargs) 114 | 115 | @token_required 116 | def add_worksheet(self, workbook_id: str, **kwargs) -> Response: 117 | """Adds a new worksheet to the workbook. 118 | 119 | https://docs.microsoft.com/en-us/graph/api/worksheetcollection-add?view=graph-rest-1.0&tabs=http 120 | 121 | Args: 122 | workbook_id (str): Excel file ID. 123 | 124 | Returns: 125 | Response: Microsoft Graph Response. 126 | """ 127 | url = "me/drive/items/{}/workbook/worksheets/add".format(workbook_id) 128 | return self._client._post(self._client.base_url + url, **kwargs) 129 | 130 | @token_required 131 | def update_worksheet(self, workbook_id: str, worksheet_id: str, **kwargs) -> Response: 132 | """Update the properties of worksheet object. 133 | 134 | https://docs.microsoft.com/en-us/graph/api/worksheet-update?view=graph-rest-1.0&tabs=http 135 | 136 | Args: 137 | workbook_id (str): Excel file ID. 138 | worksheet_id (str): Excel worksheet ID. 139 | 140 | Returns: 141 | Response: Microsoft Graph Response. 142 | """ 143 | url = "me/drive/items/{}/workbook/worksheets/{}".format(workbook_id, quote_plus(worksheet_id)) 144 | return self._client._patch(self._client.base_url + url, **kwargs) 145 | 146 | @token_required 147 | def list_charts(self, workbook_id: str, worksheet_id: str, params: dict = None, **kwargs) -> Response: 148 | """Retrieve a list of chart objects. 149 | 150 | https://docs.microsoft.com/en-us/graph/api/worksheet-list-charts?view=graph-rest-1.0&tabs=http 151 | 152 | Args: 153 | workbook_id (str): Excel file ID. 154 | worksheet_id (str): Excel worksheet ID. 155 | params (dict, optional): Query. Defaults to None. 156 | 157 | Returns: 158 | Response: Microsoft Graph Response. 159 | """ 160 | url = "me/drive/items/{}/workbook/worksheets/{}/charts".format(workbook_id, quote_plus(worksheet_id)) 161 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 162 | 163 | @token_required 164 | def add_chart(self, workbook_id: str, worksheet_id: str, **kwargs) -> Response: 165 | """Creates a new chart. 166 | 167 | https://docs.microsoft.com/en-us/graph/api/chartcollection-add?view=graph-rest-1.0&tabs=http 168 | 169 | Args: 170 | workbook_id (str): Excel file ID. 171 | worksheet_id (str): Excel worksheet ID. 172 | 173 | Returns: 174 | Response: Microsoft Graph Response. 175 | """ 176 | url = "me/drive/items/{}/workbook/worksheets/{}/charts/add".format(workbook_id, quote_plus(worksheet_id)) 177 | return self._client._post(self._client.base_url + url, **kwargs) 178 | 179 | @token_required 180 | def list_tables(self, workbook_id: str, params: dict = None, **kwargs) -> Response: 181 | """Retrieve a list of table objects. 182 | 183 | https://docs.microsoft.com/en-us/graph/api/workbook-list-tables?view=graph-rest-1.0&tabs=http 184 | 185 | Args: 186 | workbook_id (str): Excel file ID. 187 | params (dict, optional): Query. Defaults to None. 188 | 189 | Returns: 190 | Response: Microsoft Graph Response. 191 | """ 192 | url = "me/drive/items/{}/workbook/tables".format(workbook_id) 193 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 194 | 195 | @token_required 196 | def add_table(self, workbook_id: str, **kwargs) -> Response: 197 | """Create a new table. 198 | 199 | https://docs.microsoft.com/en-us/graph/api/tablecollection-add?view=graph-rest-1.0&tabs=http 200 | 201 | Args: 202 | workbook_id (str): Excel file ID. 203 | 204 | Returns: 205 | Response: Microsoft Graph Response. 206 | """ 207 | url = "me/drive/items/{}/workbook/tables/add".format(workbook_id) 208 | return self._client._post(self._client.base_url + url, **kwargs) 209 | 210 | @token_required 211 | def create_table_column(self, workbook_id: str, worksheet_id: str, table_id: str, **kwargs) -> Response: 212 | """Create a new TableColumn. 213 | 214 | https://docs.microsoft.com/en-us/graph/api/table-post-columns?view=graph-rest-1.0&tabs=http 215 | 216 | Args: 217 | workbook_id (str): Excel file ID. 218 | worksheet_id (str): Excel worksheet ID. 219 | table_id (str): Excel table ID. 220 | 221 | Returns: 222 | Response: Microsoft Graph Response. 223 | """ 224 | url = "me/drive/items/{}/workbook/worksheets/{}/tables/{}/columns".format( 225 | workbook_id, quote_plus(worksheet_id), table_id 226 | ) 227 | return self._client._post(self._client.base_url + url, **kwargs) 228 | 229 | @token_required 230 | def create_table_row(self, workbook_id: str, worksheet_id: str, table_id: str, **kwargs) -> Response: 231 | """Adds rows to the end of a table. 232 | 233 | https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http 234 | 235 | Args: 236 | workbook_id (str): Excel file ID. 237 | worksheet_id (str): Excel worksheet ID. 238 | table_id (str): Excel table ID. 239 | 240 | Returns: 241 | Response: Microsoft Graph Response. 242 | """ 243 | url = "me/drive/items/{}/workbook/worksheets/{}/tables/{}/rows".format( 244 | workbook_id, quote_plus(worksheet_id), table_id 245 | ) 246 | return self._client._post(self._client.base_url + url, **kwargs) 247 | 248 | @token_required 249 | def list_table_rows(self, workbook_id: str, table_id: str, params: dict = None, **kwargs) -> Response: 250 | """Retrieve a list of tablerow objects. 251 | 252 | https://docs.microsoft.com/en-us/graph/api/table-list-rows?view=graph-rest-1.0&tabs=http 253 | 254 | Args: 255 | workbook_id (str): Excel file ID. 256 | table_id (str): Excel table ID. 257 | params (dict, optional): Query. Defaults to None. 258 | 259 | Returns: 260 | Response: Microsoft Graph Response. 261 | """ 262 | url = "me/drive/items/{}/workbook/tables/{}/rows".format(workbook_id, table_id) 263 | return self._client._get(self._client.base_url + url, params=params, **kwargs) 264 | 265 | @token_required 266 | def get_range(self, workbook_id: str, worksheet_id: str, address: str, **kwargs) -> Response: 267 | """Gets the range object specified by the address or name. 268 | 269 | https://docs.microsoft.com/en-us/graph/api/worksheet-range?view=graph-rest-1.0&tabs=http 270 | 271 | Args: 272 | workbook_id (str): Excel file ID. 273 | worksheet_id (str): Excel worksheet ID. 274 | address (str): Address. 275 | 276 | Returns: 277 | Response: Microsoft Graph Response. 278 | """ 279 | url = "me/drive/items/{}/workbook/worksheets/{}/range(address='{}')".format( 280 | workbook_id, quote_plus(worksheet_id), address 281 | ) 282 | return self._client._get(self._client.base_url + url, **kwargs) 283 | 284 | @token_required 285 | def get_used_range(self, workbook_id: str, worksheet_id: str, **kwargs) -> Response: 286 | """The used range is the smallest range that encompasses any cells that have a value or formatting assigned to 287 | them. If the worksheet is blank, this function will return the top left cell. 288 | 289 | https://docs.microsoft.com/en-us/graph/api/worksheet-usedrange?view=graph-rest-1.0&tabs=http 290 | 291 | Args: 292 | workbook_id (str): Excel file ID. 293 | worksheet_id (str): Excel worksheet ID. 294 | 295 | Returns: 296 | Response: Microsoft Graph Response. 297 | """ 298 | url = "me/drive/items/{}/workbook/worksheets/{}/usedRange".format(workbook_id, quote_plus(worksheet_id)) 299 | return self._client._get(self._client.base_url + url, **kwargs) 300 | 301 | @token_required 302 | @workbook_session_id_required 303 | def update_range(self, workbook_id: str, worksheet_id: str, address: str, **kwargs) -> Response: 304 | """Update the properties of range object. 305 | 306 | https://docs.microsoft.com/en-us/graph/api/range-update?view=graph-rest-1.0&tabs=http 307 | 308 | Args: 309 | workbook_id (str): Excel file ID. 310 | worksheet_id (str): Excel worksheet ID. 311 | address (str): Address. 312 | 313 | Returns: 314 | Response: Microsoft Graph Response. 315 | """ 316 | headers = {"workbook-session-id": self._client.workbook_session_id} 317 | url = "me/drive/items/{}/workbook/worksheets/{}/range(address='{}')".format( 318 | workbook_id, quote_plus(worksheet_id), address 319 | ) 320 | return self._client._patch(self._client.base_url + url, headers=headers, **kwargs) 321 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "microsoftgraph-python" 3 | version = "1.1.8.1" 4 | description = "API wrapper for Microsoft Graph written in Python" 5 | authors = ["Gearplug Team "] 6 | license = "MIT" 7 | readme = "README.md" 8 | packages = [{include = "microsoftgraph"}] 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.7" 12 | requests = "^2.26.0" 13 | 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | 19 | --------------------------------------------------------------------------------